🎬 That's a Wrap for GraphQLConf 2024! • Watch the Videos • Check out the recorded talks and workshops
Passing Arguments

Just like a REST API, it’s common to pass arguments to an endpoint in a GraphQL API. By defining the arguments in the schema language, typechecking happens automatically. Each argument must be named and have a type. For example, in the Basic Types documentation we had an endpoint called rollThreeDice:

type Query {
  rollThreeDice: [Int]
}

Instead of hard coding “three”, we might want a more general function that rolls numDice dice, each of which have numSides sides. We can add arguments to the GraphQL schema language like this:

type Query {
  rollDice(numDice: Int!, numSides: Int): [Int]
}

The exclamation point in Int! indicates that numDice can’t be null, which means we can skip a bit of validation logic to make our server code simpler. We can let numSides be null and assume that by default a die has 6 sides.

So far, our resolver functions took no arguments. When a resolver takes arguments, they are passed as one “args” object, as the first argument to the function. So rollDice could be implemented as:

const root = {
  rollDice(args) {
    const output = [];
    for (const i = 0; i < args.numDice; i++) {
      output.push(1 + Math.floor(Math.random() * (args.numSides || 6)));
    }
    return output;
  },
};

It’s convenient to use ES6 destructuring assignment for these parameters, since you know what format they will be. So we can also write rollDice as

const root = {
  rollDice({ numDice, numSides }) {
    const output = [];
    for (const i = 0; i < numDice; i++) {
      output.push(1 + Math.floor(Math.random() * (numSides || 6)));
    }
    return output;
  },
};

If you’re familiar with destructuring, this is a bit nicer because the line of code where rollDice is defined tells you about what the arguments are.

The entire code for a server that hosts this rollDice API is:

const express = require('express');
const { createHandler } = require('graphql-http/lib/use/express');
const { buildSchema } = require('graphql');
 
// Construct a schema, using GraphQL schema language
const schema = buildSchema(/* GraphQL */ `
  type Query {
    rollDice(numDice: Int!, numSides: Int): [Int]
  }
`);
 
// The root provides a resolver function for each API endpoint
const root = {
  rollDice({ numDice, numSides }) {
    const output = [];
    for (const i = 0; i < numDice; i++) {
      output.push(1 + Math.floor(Math.random() * (numSides || 6)));
    }
    return output;
  },
};
 
const app = express();
app.all(
  '/graphql',
  createHandler({
    schema: schema,
    rootValue: root,
  }),
);
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

When you call this API, you have to pass each argument by name. So for the server above, you could issue this GraphQL query to roll three six-sided dice:

{
  rollDice(numDice: 3, numSides: 6)
}

If you run this code with node server.js and browse to http://localhost:4000/graphql you can try out this API.

When you’re passing arguments in code, it’s generally better to avoid constructing the whole query string yourself. Instead, you can use $ syntax to define variables in your query, and pass the variables as a separate map.

For example, some JavaScript code that calls our server above is:

const dice = 3;
const sides = 6;
const query = /* GraphQL */ `
  query RollDice($dice: Int!, $sides: Int) {
    rollDice(numDice: $dice, numSides: $sides)
  }
`;
 
fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    query,
    variables: { dice, sides },
  }),
})
  .then((r) => r.json())
  .then((data) => console.log('data returned:', data));

Using $dice and $sides as variables in GraphQL means we don’t have to worry about escaping on the client side.

With basic types and argument passing, you can implement anything you can implement in a REST API. But GraphQL supports even more powerful queries. You can replace multiple API calls with a single API call if you learn how to define your own object types.