In this lesson we will learn the basics of writing GraphQL APIs.
We will use apollo-server-express for middleware and graphql-tools for building our schema.
npm install -s apollo-server-express body-parser graphql graphql-tools
npm install -D @types/graphql
The core graphql implementation is written by Facebook
The server middleware and graphql-tools
packages we have chosen to use are part of Apollo: a family of tools that enable developers to get the most out of GraphQL, run by a company called Meteor, who build open source tools and commercial services.
- https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-express
- https://github.com/apollographql/graphql-tools
These are both written in Typescript.
Note that Apollo ship many server middlewares including:
- apollo-server-azure-functions
- apollo-server-lambda
Replace or amend app.ts to:
- Build a basic schema and resolver map
- Run GraphQL using the
graphqlExpress
middleware function - Run the Graphiql IDE using the
graphiqlExpress
middleware function
import { graphiqlExpress, graphqlExpress } from "apollo-server-express";
import bodyParser from "body-parser";
import express from "express";
import { makeExecutableSchema } from "graphql-tools";
// schema using GraphQL schema language
const typeDefs = `
type Query {
hello: String
}
`;
// resolver map to execute the schema
const resolvers: any = {
Query: {
hello: () => "Hello world!"
}
};
// build the executable schema
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
const app = express();
// set up graphql
app.use(
"/graphql",
bodyParser.json(),
graphqlExpress({
schema
})
);
// set up the graphql ide
app.use(
"/graphiql",
graphiqlExpress({
endpointURL: "/graphql"
})
);
app.listen(3000, () =>
console.log(`Open http://localhost:3000/graphiql to run queries`)
);
- Run the app
- Open http://localhost:3000/graphiql
- Run the hello query:
{ hello }
✔ Explore the IDE autocomplete
✔ Explore the IDE schema / documentation explorer
*(also termed IDL - Interface Definition Language)
http://graphql.org/learn/schema/
No coding in this step, just reading.
const typeDefs = `
type User {
id: String
name: String
}
type Query {
user(id: String): User
}
`);
const resolvers: any = {
Query: {
user: (source: any, args: any) => fakeDatabase[args.id]
}
}
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
const userType = new graphql.GraphQLObjectType({
name: "User",
fields: {
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString }
}
});
const queryType = new graphql.GraphQLObjectType({
name: "Query",
fields: {
user: {
type: userType,
args: {
id: { type: graphql.GraphQLString }
},
resolve: (source: any, args: any) => fakeDatabase[args.id]
}
}
});
var schema = new graphql.GraphQLSchema({ query: queryType });
✔ Argue pros and cons
✔ Accept it has been declared that we shall go forth using SDL
http://graphql.org/learn/queries/#directives
-
Change your schema to include the
@deprecated
directive against thehello
query method and add a replacementhelloWorld
which accepts afrom
argumentconst typeDefs = ` type Query { hello: String @deprecated(reason: "do not use this method, use helloWorld") helloWorld(from: String): String } `;
-
Add a resolver for the new method
const resolvers: any = { Query: { hello: () => "Hello world!", helloWorld: (source: any, args: any) => `Hello world from ${args.from}!` } };
-
Run it..
{ hello helloWorld(from: "[Your Name Here]") }
✔ Check the behaviour of the GraphiQL IDE (and generated schema doc) with the deprecated field and the new query method.
✔ Check your argument works as expected.
You're probably wondering what the first resolver argument source: any
is. This is used to pass down the parent object for resolution within a hierarchical object graph, but since Query
is a top level object, source
is undefined
. You'll use this resolver argument later.
In the typeDefs
schema string add the !
operator after the type.
helloWorld(from: String!): String
In the resolvers
map...
helloWorld: (source: any, { from }: { from: string }) => `Hello world from ${from}!`,
✔ Test the query parsing behaviour of the GraphiQL IDE when the required argument is missing: http://localhost:3000/graphiql?query=%7B%0A%20%20helloWorld%0A%7D%0A
const typeDefs = `
"""
The top level query type
"""
type Query {
hello: String @deprecated(reason: "do not use this method, use helloWorld")
"""
Returns a _custom_ hello world message
"""
helloWorld(from: String!): String
}
`;
✔ Check your documentation is shown in the schema explorer
http://graphql.org/learn/execution/
✔ Resolvers receive 4 arguments
✔ Resolvers can be async (functions that return a promise)
✔ Scalar coercion (e.g. resolving numbers to schema enums)
✔ Resolvers can return lists (of things or promises)
http://graphql.org/graphql-js/graphql-clients/
- The request can be a GET or POST, depending on server configuration.
- The request body, if used, is JSON; but the
query
field is a string (SDL is not JSON). - The response is JSON.
{ "query": "{helloWorld(from:\"Sam\")}" }
{ "data": { "helloWorld": "Hello world from Sam!" } }
You do not need to interpolate and escape parameter values into the
query
string (yuck!). Instead, bind parameters using$paramName
syntax and supply avariables
map.
Try using the variables tab in the Graphiql IDE
query($name: String!) {
helloWorld(from: $name)
}
{
"name": "Sam"
}
This generates a request body which includes both query
(SDL string) and variables
(JSON map) properties set:
{
"query": "query ($name: String!) {helloWorld(from: $name)}",
"variables": { "name": "Sam" }
}