Writing queries

ArangoDB provides the query template string handler(or template tag)to make it easy to write and execute AQL queriesin your Foxx services:

  1. const { query } = require("@arangodb");
  2. const max = 13;
  3. const oddNumbers = query`
  4. FOR i IN 1..${max}
  5. FILTER i % 2 == 1
  6. RETURN i
  7. `.toArray();
  8. console.log(oddNumbers); // 1,3,5,7,9,11,13

Any values passed via interpolation (i.e. using the ${expression} syntax)are passed to ArangoDB asAQL bind parameters,so you don’t have to worry about escaping them in order to protect againstinjection attacks in user-supplied data.

The result of the executed query isan ArangoDB array cursor.You can extract all query results using the toArray() method orstep through the result set using the next() method.

You can also consume a cursor with a for-loop:

  1. const cursor = query`
  2. FOR i IN 1..5
  3. RETURN i
  4. `;
  5. for (const item of cursor) {
  6. console.log(item);
  7. }

Using collections

When working with collections in your service you generallywant to avoid hardcoding exact collection names. But if you pass acollection name directly to a query it will be treated as a string:

  1. // THIS DOES NOT WORK
  2. const users = module.context.collectionName("users");
  3. // e.g. "myfoxx_users"
  4. const admins = query`
  5. FOR user IN ${users}
  6. FILTER user.isAdmin
  7. RETURN user
  8. `.toArray(); // ERROR

Instead you need to pass an ArangoDB collection object:

  1. const users = module.context.collection("users");
  2. // users is now a collection, not a string
  3. const admins = query`
  4. FOR user IN ${users}
  5. FILTER user.isAdmin
  6. RETURN user
  7. `.toArray();

Note that you don’t need to use any different syntax to usea collection in a query, but you do need to make sure the collection isan actual ArangoDB collection object rather than a plain string.

Low-level access

In addition to the query template tag, ArangoDB also providesthe aql template tag, which only generates a query objectbut doesn’t execute it:

  1. const { db, aql } = require("@arangodb");
  2. const max = 7;
  3. const query = aql`
  4. FOR i IN 1..${max}
  5. RETURN i
  6. `;
  7. const numbers = db._query(query).toArray();

You can also use the db._query method to execute queries usingplain strings and passing the bind parameters as an object:

  1. // Note the lack of a tag, this is a normal string
  2. const query = `
  3. FOR user IN @@users
  4. FILTER user.isAdmin
  5. RETURN user
  6. `;
  7. const admins = db._query(query, {
  8. // We're passing a string instead of a collection
  9. // because this is an explicit collection bind parameter
  10. // using the AQL double-at notation
  11. "@users": module.context.collectionName("users")
  12. }).toArray();

Note that when using plain strings as queries ArangoDB providesno safeguards to prevent accidental AQL injections:

  1. // Malicious user input where you might expect a number
  2. const evil = "1 FOR u IN myfoxx_users REMOVE u IN myfoxx_users";
  3. // DO NOT DO THIS
  4. const numbers = db._query(`
  5. FOR i IN 1..${evil}
  6. RETURN i
  7. `).toArray();
  8. // Actual query executed by the code:
  9. // FOR i IN i..1
  10. // FOR u IN myfoxx_users
  11. // REMOVE u IN myfoxx_users
  12. // RETURN i

If possible, you should always use the query or aql template tagsrather than passing raw query strings to db._query directly.

AQL fragments

If you need to insert AQL snippets dynamically, you can still usethe query template tag by using the aql.literal helper function tomark the snippet as a raw AQL fragment:

  1. const filter = aql.literal(
  2. adminsOnly ? 'FILTER user.isAdmin' : ''
  3. );
  4. const result = query`
  5. FOR user IN ${users}
  6. ${filter}
  7. RETURN user
  8. `.toArray();

Both the query and aql template tags understand fragments markedwith the aql.literal helper and inline them directly into the queryinstead of converting them to bind parameters.

Note that because the aql.literal helper takes a raw string as argumentthe same security implications apply to it as when writing raw AQL queriesusing plain strings:

  1. // Malicious user input where you might expect a condition
  2. const evil = "true REMOVE u IN myfoxx_users";
  3. // DO NOT DO THIS
  4. const filter = aql.literal(`FILTER ${evil}`);
  5. const result = query`
  6. FOR user IN ${users}
  7. ${filter}
  8. RETURN user
  9. `.toArray();
  10. // Actual query executed by the code:
  11. // FOR user IN myfoxx_users
  12. // FILTER true
  13. // REMOVE user IN myfoxx_users
  14. // RETURN user

A typical scenario that might result in an exploit like this is takingarbitrary strings from a search UI to filter or sort results by a field name.Make sure to restrict what values you accept.

Managing queries in your service

In many cases it may be initially more convenient to perform queriesright where you use their results:

  1. router.get("/emails", (req, res) => {
  2. res.json(query`
  3. FOR u IN ${users}
  4. FILTER u.active
  5. RETURN u.email
  6. `.toArray())
  7. });

However to help testability and make the queries more reusable,it’s often a good idea to move them out of your request handlersinto separate functions, e.g.:

  1. // in queries/get-user-emails.js
  2. "use strict";
  3. const { query, aql } = require("@arangodb");
  4. const users = module.context.collection("users");
  5. module.exports = (activeOnly = true) => query`
  6. FOR user IN ${users}
  7. ${aql.literal(activeOnly ? "FILTER user.active" : "")}
  8. RETURN user.email
  9. `.toArray();
  10. // in your router
  11. const getUserEmails = require("../queries/get-user-emails");
  12. router.get("/active-emails", (req, res) => {
  13. res.json(getUserEmails(true));
  14. });
  15. router.get("/all-emails", (req, res) => {
  16. res.json(getUserEmails(false));
  17. });