Writing queries

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

    The result of the executed query is . You can extract all query results using the toArray() method or step 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. }

    It is also possible to pass options to the query helper:

    1. const cursor = query({ fullCount: true })`
    2. FOR i IN 1..1000
    3. LIMIT 0, 100
    4. RETURN i
    5. `;
    6. const { fullCount } = cursor.getExtra().stats;
    7. console.log(`${fullCount} total`);
    8. console.log(cursor.toArray());

    When working with collections in your service you generally want to avoid hardcoding exact collection names. But if you pass a collection 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

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

    In addition to the query template tag, ArangoDB also provides the aql template tag, which only generates a query object but 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. const numbers = db._query(query).toArray();

    You can also use the method to execute queries using plain 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 provides no 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 tags rather than passing raw query strings to db._query directly.

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

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

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

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

    In many cases it may be initially more convenient to perform queries right 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 and make the queries more reusable, it’s often a good idea to move them out of your request handlers into 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. });