Defining schema
You start with a schema - a description of what the clients can ask. For example if we have a "product" item we can describe like this below.
1 | const typeDefs = ` |
For now this is a simple JavaScript string.
Simple queries
It is not enough to describe the Product
schema. We also should describe queries that return it. Let us return a single product by id, or list of all products. I am also putting comments into the schema string as quoted lines or after #
character. You can find more schema examples in the graph-tools repo.
1 | const typeDefs = ` |
Resolvers
Each query should return actual data somehow. We need to map each query to a resolver function. Let us use hard coded data for now. Here is our data
1 | const products = [{ |
Here are the two resolvers we need - one for allProducts
and another for getProduct
. These methods map by name to the properties inside type Query
in our type definitions.
1 | const resolvers = { |
Server
We now can use graphql-tools to make middleware for an Express.js server.
1 | import express from 'express' |
Once we start the server, we can either the graphical interface (open your browsers at localhost:3000
) or do regular GET
requests from the command line. I am using excellent httpie client.
1 | http http://localhost:3000/g?query={allProducts{id}} |
Excellent - the result is placed into data
key, and we have only asked for ids. Let us ask for more fields; we can ask for name
and qty
of each product. Note that from the command line I need to escape commas
1 | $ http http://localhost:3000/g?query={allProducts{id\,name\,qty}} |
If I ask for a non-existent property, instead of data
field, the server will return a list of errors.
1 | $ http http://localhost:3000/g?query={allProducts{foo}} |
Let us ask for a specific item (and we only ask for id
and name
properties).
1 | $ http http://localhost:3000/g?query={getProduct\(id:1\){id\,name}} |
It is kind of annoying to escape special URL symbols, like ,
and ()
when making these requests from command line.
Graphql files
Instead of using strings, we can place our GraphQL schema definitions into .graphql
files and use a helper module to import them. I will use graphql-import to import the schema file (and the default VSCode syntax highlighting)
1 | "describes Product item" |
1 | import { importSchema } from 'graphql-import' |
We can even split the schema file further. Place "Person" definition in person.graphql
1 | "describes Product item" |
and import "Person" using import
in the comment from schema.graphql
1 | # import Person from "person.graphql" |
and now the GraphQL schemas are organized much better.
Connecting query to the database
Next, we need to fetch real data from the database. I went through the Build GraphQL APIs with Node.js on MongoDB Egghead.io course, coding along. You can find the working version in the bahmutov/graphql-node-mongo-egghead-course repository. The master
branch has a local MongoDB database (via Mongoose ORM). As an experiment, I also implemented the database API by using objection.js ORM on top of sqlite3
. Check out branch objection-1
to see the code. Here is a typical resolver:
1 | import Product from './models/product' // objection model |
Quite nice - each resolver can be a Promise-returning function that maps nicely to the database query.
Mutations
Having just static queries is not enough. How do we write mutations using GraphQL that add new data items or update existing ones? We write type Mutation
type definition! For example, here are typical queries for adding / deleting / updating a Product
type.
1 | input ProductInput { |
The implementation in resolvers is very similar to the queries.
1 | export const resolvers = { |
Perfect 🎉
Apollo Server v2
While you can use any HTTP server with GraphQL endpoint, it makes sense to try something that is optimized for serving GraphQL queries. Apollo Server is one such thing. Its v2 promises to be pretty good, so I am trying a release candidate (npm i apollo-server@rc
). You can run its stand alone like this
1 | const { ApolloServer, gql } = require('apollo-server') |
Or you can apply ApolloServer v2 as middleware to existing server.
1 | const express = require('express') |
If you get an API key from a Apollo organization, you can enable performance tracing on your server which is very important, because all queries go through the same endpoint, and so "traditional" performance monitoring is not going to work very well.
Performance
You can turn on performance tracing in some GraphQL servers. For example
1 | const server = new ApolloServer({ |
Then make a query request like
1 | $ http :3000/graphql?query={hello |
and you should see performance information with the result
1 | { |
This is what ApolloEngine uses to show performance stats for the GraphQL queries.
Links
There are many excellent resources for learning GraphQL. Some of the ones I read are
- Build GraphQL APIs with Node.js on MongoDB Egghead.io course
- Front end developers guide to GraphQL slides
- Authentication & Authorization in GraphQL slides
Extras
I have several other blog posts that are trying a technology. Check out these links