GraphQL Tutorial — Getting Started
As originally posted at Jobstart.
Introduction
GraphQL is one of the most exciting technologies gaining developer attention in 2017. A viable alternative to RESTful APIs, GraphQL APIs provide a much more succinct and expressive way to read and write relational data between client and server.
Two of the more exciting pieces of technology within the GraphQL ecosystem are graphql-server
and apollo-client
, built by the folks at Apollo. In this post, we will focus on discussing high-level GraphQL concepts and getting a basic server up and running with graphql-server
.
You can get the finished source code here
Architecture
At a high level, GraphQL can be thought of as specification for creating strongly-typed transport-agnostic APIs. Before we dive into the nitty-gritty of our server, let’s take a quick moment to cover some basic GraphQL nomenclature:
- A Query is a read-only operation made against a GraphQL server.
- A Mutation is a read-write operation made against a GraphQL server.
- A Resolver provides a mapping between a portion of a GraphQL operation and actual backend code responsible for handling it (similar to a controller in a RESTful MVC backend).
- A Schema defines what queries and mutations can be performed against a GraphQL server, what each query or mutation takes as input, and what each query or mutation returns as output.
- A Type defines the shape of output / response data that can be returned from the GraphQL server, including fields that are edges to other Types.
- An Input is like a Type, but defines the shape of input data that is sent to a GraphQL server.
- A Scalar is a primitive Type, such as a
String
,Int
,Boolean
,Float
, etc.
GraphQL’s domain of concern begins at the Client where the request is assembled, and ends at the Resolvers where the request is translated into actual backend code written in a language and paradigm of your choosing. GraphQL cares nothing about how you store your data or how your frontend works. It is simply a paradigm and language for making requests and rendering responses. With GraphQL, you can think of your internal data sources as a graph of relational data and traverse those relationships with a natural and expressive language.
First steps
Let’s spin up a basic “hello world” server so that we can get our hands dirty with an actual implementation.
If you don’t already have NodeJS configured on your system, you will need to install it.
We will be using esnext-node-starterkit as a project boilerplate, so let’s go ahead and clone that now:
git clone git@github.com:thebigredgeek/esnext-node-starterkit.git graphql-demo
cd graphql-demo
npm install
Now, let’s quickly review our project structure:
src/index.js
will be the entry point of our serversrc/resolvers.js
will be where our Resolvers livesrc/schema.js
will be where our Schema lives
Our boilerplate comes with src/index.js
. Let's go ahead and create the other two files:
touch src/resolvers.js
touch src/schema.js
Schema
The top-level of a GraphQL Schema is always defined the same way, with a schema
declaration, so let's write that at the top of our src/schema.js
file:
/* src/schema.js */
const schema = `
schema {
query: Query
}
`;
The schema declaration will define a query
field. In the declaration above, we are mapping query
to an additional Type called Query
, which will tell GraphQL what root fields our server can return, what their resolvers will take as input, and the type of the field. Let's define a helloWorld
field on our initial Query type:
/* src/schema.js */...const Query = `
type Query {
helloWorld: String!
}
`;
Notice that, like in our schema definition, we are mapping helloWorld
to a separate Type, in the case String
. String
is a pre-defined Scalar Type in GraphQL, so we don't need to define it ourselves. The exclamation mark at the end of String
denotes that our helloWorld
field will always be a String, and never null
.
Finally, let’s export our GraphQL definitions:
/* src/schema.js */...export default [
schema,
Query
];
Now that we have defined our Schema, let’s move on to our Resolvers.
Resolvers
“Resolvers” is a bit of a misnomer here. Because we only have a single field helloWorld
of Type String
, we only need a single Resolver to tell GraphQL how to fetch helloWorld
. Let's go ahead and write the Resolver now:
/* src/resolvers.js */
export default {
Query: {
// Our only Resolver, which belongs to the `Query`
// Type that we defined before
helloWorld: () => 'Hello World!' // Returns a String
}
};
And that’s it! We don’t need to write any more code for our resolver to work. As an aside, it is worth noting that in NodeJS we can return a Promise from any Resolver if we need to do async work to fetch data. GraphQL will yield to the Promise and pass through whatever it resolves to, or throw an error for whatever it rejects as. In real world apps, we will usually call a model method from our resolver which returns a Promise that resolves to a shape that matches the Type returned by the Resolver (more on that in the next post).
Let’s bring it all together with Express and a bit of Express middleware and take our new GraphQL server for a spin!
To Hello World…
As stated before, GraphQL is simply a language for reading and writing data to and from a server. There are a variety of server-side implementations for NodeJS, Golang, Elixir, Ruby, Python, Java, and more. Because we are working with NodeJS in this tutorial, we will be using the popular NodeJS implementation graphql-server written by the folks at Apollo and maintained by their open source community.
To begin with, let’s install a few necessary packages to wire everything together:
npm install express body-parser graphql graphql-server-express graphql-tools
Now let’s write our src/index.js
file and get our server up and running for a test:
/* src/index.js */
import 'source-map-support/register';
import 'babel-polyfill';
import express from 'express';
import bodyParser from 'body-parser';
import { graphqlExpress } from 'graphql-server-express';
import { makeExecutableSchema } from 'graphql-tools';import resolvers from './resolvers';
import schema from './schema';const app = express();const executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers
});app.post('/graphql',
bodyParser.json(),
graphqlExpress(() => ({
schema: executableSchema
}))
);app.listen(8080);
Awesome! Let’s load up the server:
npm run watch
Our query will look like this:
query demo {
helloWorld
}
Let’s test it out on a second terminal tab:
curl -X POST http://localhost:8080/graphql -d '{"query": "query demo { helloWorld }" }' -H 'Content-Type: application/json'
You should see output!
{
"data": {
"helloWorld": "Hello World!"
}
}
And beyond…
This is a rather simple demonstration. In a real world GraphQL backend, you will have Types that reference one another. Let’s make a quick modification to our server to see how that can work. Let’s create a new Hello
type that references a new World
type, rename the helloWorld
query to hello
, and return the Hello
type from our hello
query:
First, in our src/schema.js
file, let's add the new Types and the relationship:
/* src/schema.js */
const schema = `
schema {
query: Query
}
`;const Query = `
type Query {
hello: Hello!
}
`;const Hello = `
type Hello {
world: World!
}
`;const World = `
type World {
text: String!
}
`;export default [
schema,
Query,
Hello,
World
];
Next, we need to define a couple of extra resolvers in src/resolvers.js
:
/* src/resolvers.js */
class Hello {
constructor () {
this.world = new World();
}
}class World {
constructor () {
this.text = 'Hello world!';
}
}export default {
Query: {
// Our only Resolver, which belongs to the `Query`
// Type that we defined before
hello: () => new Hello() // Return an instance of the Hello class
},
// Uppercase "Hello", the same as the name of the Type in `src/schema.js`
Hello: {
// Tell GraphQL how to get from an instance of Hello to `instance.world`
world: hello => hello.world
}
};
Let’s also add a better tool to test our GraphQL server with a simple change to src/index.js
:
/* src/index.js */
...
// Import graphiqlExpress, a UI tool for testing GraphQL APIs
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express';...// Add GraphiQL Middleware
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));app.listen(8080);
Let’s test it again with our new structure and our new tool. Goto http://localhost:8080/graphiql.
Our query will look like this:
query {
hello {
world {
text
}
}
}
Now enter the above query, and click the run button, and Voilà!
Nice! We are now fetching relational data! Hello
has a 1:1
relation with World
through the world
field. World
contains field text
of type String
. With GraphQL, we can traverse relational data in a declarative way. Remember, our Resolvers can return Promises, so we can go to our database and load data rather than simply returning fixture data as we are doing in our example.
In conclusion
We built a simple Hello World backend, but this is only scratching the surface with what we can do with GraphQL APIs. In the next post we will build a fully featured full-stack message feed app with an isomorphic React frontend and a GraphQL backend using Postgres as our data store, Redis for caching, and Elasticsearch for searching messages. We will cover fundamental concepts such as caching, access control, and error handling. Check back soon for a link!