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:
import {
GraphQLObjectType,
GraphQLSchema,
GraphQLList,
GraphQLInt,
GraphQLNonNull,
} from 'graphql';
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
rollDice: {
type: new GraphQLList(GraphQLInt),
args: {
numDice: { type: new GraphQLNonNull(GraphQLInt) },
numSides: { type: GraphQLInt },
},
resolve: (_, { numDice, numSides }) => {
const output = [];
for (let i = 0; i < numDice; i++) {
output.push(1 + Math.floor(Math.random() * (numSides || 6)));
}
return output;
},
},
},
}),
});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.
Default argument values
When an argument has a natural default, define that default in the schema
instead of filling it in inside every resolver. In SDL, add = value after the
argument type:
type Query {
rollDice(numDice: Int!, numSides: Int = 6): [Int]
}When constructing a schema in code, GraphQL.js v16 uses defaultValue:
const rollDiceField = {
type: new GraphQLList(GraphQLInt),
args: {
numDice: { type: new GraphQLNonNull(GraphQLInt) },
numSides: { type: GraphQLInt, defaultValue: 6 },
},
};GraphQL.js v17 also supports the more explicit default shape for new code:
const rollDiceField = {
type: new GraphQLList(GraphQLInt),
args: {
numDice: { type: new GraphQLNonNull(GraphQLInt) },
numSides: { type: GraphQLInt, default: { value: 6 } },
},
};defaultValue is the legacy way to provide an already-coerced JavaScript
value. For new code in v17, use default: { value } with the raw JavaScript
input value before coercion; on the built schema, that same case is represented
as argument.default.value. Keeping the raw value lets GraphQL.js validate the
default and report it through introspection without reconstructing the GraphQL
literal from an already-coerced value, which fixes a subtle source of incorrect
default values for some schemas. If you already have a GraphQL
literal instead, use default: { literal }. GraphQL.js now uses the literal
form internally when building a schema from SDL. defaultValue still works in
v17, but is deprecated for removal in v18.
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 (let 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 (let 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 can use code-first schema construction or SDL:
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLInt,
GraphQLList,
GraphQLSchema,
} from 'graphql';
// Construct a schema in code.
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
rollDice: {
type: new GraphQLList(GraphQLInt),
args: {
numDice: {
type: new GraphQLNonNull(GraphQLInt),
},
numSides: {
type: GraphQLInt,
},
},
resolve: (_, { numDice, numSides }) => {
const output = [];
for (let 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,
}),
);
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.