xoor logo

Integrating Apollo and Express to build a Node.js GraphQL API

July 17th, 2018

Using ES6/7 powered by Babel

Written by Maximiliano Duthey, Full Stack Developer @ XOOR

GraphQL has been gathering a lot of attention lately and at XOOR we don’t like to let things pass by. So in some of our latest work we started experimenting with it and even started using it in production for some projects. In this post we’ll see how to build a very simple API using Express and Apollo as the GraphQL server. If you’re not familiar with GraphQL yet, in a few words it can be described as:

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

So basically it’s an alternative to RESTful APIs, and the biggest advantage it has is that you can ask for exactly what you need. Sometimes with REST you have an endpoint to get user information and you get a bunch of data that’s never used. With GraphQL you can say: “I want the email and the phone number from the user with ID 99”. This was a very stupid and high level explanation of GraphQL, so I encourage you to go ahead and read a bit about it if you’d like to know more about it.

If you want to see the code for this project, it’s hosted on GitHub -> https://github.com/xoor-io/express-apollo-api

Initial server setup

The first thing we’ll do then is create a directory wherever in your file system, cd into it and init an NPM project there using npm init .

In our example we’ll be using Express as the http server and Babel so that we can write awesome ES6 and ES7. Let’s start by installing a few Babel dependencies:

npm i -D @babel/cli @babel/core @babel/node @babel/preset-env @babel/register

And in the project root we’ll create a babel config file named .babelrc with the following contents:

{
 "presets": [
   ["@babel/preset-env", {
     "targets": {
       "node": "current"
     }
   }]
 ]
}

In this case we tell Babel to compile code into Javascript that the “current” Node.js version understands. Feel free to set there a specific version instead if you want to. More info about preset-env here.

Next on our list is a good friend of all Node.js devs: Nodemon. Go ahead and install it as a dev dependency:

npm i -D nodemon

Next thing is updating the start script on our package.json file. Since we’re coding with ES6/7, we need to use babel-node to run our code. So replace the default “start” script in the package.json file with the following one:

NODE_ENV=development nodemon src/app.js --exec babel-node

With this start script, when running npm start we’ll be lifting the server using nodemon and calling the app.js script with babel-node .

Now we’re ready to install express and cors to start building the server base structure:

npm i express cors

Then we create an src directory in the root of our project where all the API code will reside. Within this directory we’ll create 2 files: server.js and app.js. The first one will contain all the express app configuration and export it, so that it’s easier to create unit tests later. The latter will be the main entry point for the app and will import server.js to run the app.

The server.js file will look like this:

// HTTP SERVER
import express from 'express';
import cors from 'cors';
const app = express();
function setPort(port = 5000) {
app.set('port', parseInt(port, 10));
}
function listen() {
const port = app.get('port') || 5000;
app.listen(port, () => {
console.log(`The server is running and listening at http://localhost:${port}`);
});
}
app.use(cors({
origin: '*', // Be sure to switch to your production domain
optionsSuccessStatus: 200
}));
// Endpoint to check if the API is running
app.get('/api/status', (req, res) => {
res.send({ status: 'ok' });
});
export default {
getApp: () => app,
setPort,
listen
};
view raw server.js hosted with ❤ by GitHub

Not very complex, specially for those familiar with Express. We initialize the app by using the express() function. Then we configure the cors middleware allowing all origins to query our API. This is for development purposes only, in production you MUST make sure that the allowed origins are trusted ones only. We also define a utility endpoint to check if the API is up and running and finally export the app, as well as two utility methods to change the app port and to start listening on the configured port.

And the app.js will import the previous code and call the listen() function to start the http server:

import server from './server';
server.listen();
export default server;
view raw app.js hosted with ❤ by GitHub

Up to this point we’ll be able to run our server. So open up your console and run npm start . The app will be listening on the port 5000 by default. You can check that it’s working by using the GET /api/status endpoint.

So far we didn’t do anything special, we just created an express application with a dummy endpoint. Let’s start building cooler things by adding Apollo to our app.

Configuring Apollo GraphQL

First thing on our list will be installing a few needed dependencies:

npm i -D apollo-server-express body-parser graphql graphql-tools lodash

Next step is setting up a config dir where we’ll store some of our common configuration for the app. You can use other packages to handle this, we’ll just write a very simple module. So let’s create a config directory withinsrc and let’s create an index.js file there with the following contents:

const commonConfig = {
env: process.env.NODE_ENV || 'development',
port: parseInt(process.env.PORT, 10) || 5000,
corsDomain: process.env.CORS_DOMAIN || '*'
};
export default commonConfig;
view raw config.js hosted with ❤ by GitHub

We’ll update our server.js file to use this config file later.

Now we’re ready to start integrating Apollo into our app. Let’s create a src/graphql directory and put an index.js file and a schema directory inside. The first one will contain the Apollo middleware configuration for our express server. The schema directory will contain all the GraphQL schema definitions.

With GraphQL it all relates to schema definitions. More about schemas and types here -> https://graphql.org/learn/schema/

Now open the src/graphql/index.js file and put the following code inside:

import bodyParser from 'body-parser';
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
import config from '../config';
import schema from './schema';
export default (app) => {
if (config.env === 'development') {
app.use('/api/graphiql', graphiqlExpress({ endpointURL: '/api' }));
}
app.use(
'/api',
bodyParser.json(),
graphqlExpress(() => ({
schema,
debug: config.env === 'development'
}))
);
};
view raw index.js hosted with ❤ by GitHub

Important things being done here:

  • We’re importing dependencies from apollo-server-express, which work as regular express middlewares
  • We’re importing the graphql schema. We didn’t define this yet, but we’ll be doing so soon
  • Whenever we’re running on development mode, we’re using the graphiQL middleware. This is a very useful web interface to interact with our GraphQL API, it shows the schema, available queries/mutations and has autocomplete capabilities
  • Finally we define the /api endpoint where our API will exist. There we use the graphqlExpress middleware and we pass the schema as an argument to it.

That’s it! As you can see, all we need to do to have a schema defined. The rest is quite simple. So now let’s see what that schema thing is and how to define one.

The GraphQL Schema

The first thing we’ll define is the entry point of the schema definition, the index.js file inside the src/graphql/schema directory. So create that file and paste this on it:

import fs from 'fs';
import path from 'path';
import { makeExecutableSchema } from 'graphql-tools';
import { merge } from 'lodash';
const Query = `
type Query {
status: String
}
`;
const Mutation = `
type Mutation {
_empty: String
}
`;
let resolvers = {
Query: {
status: () => 'ok'
}
};
const typeDefs = [Query, Mutation];
// Read the current directory and load types and resolvers automatically
fs.readdirSync(__dirname)
.filter(dir => (dir.indexOf('.') < 0))
.forEach((dir) => {
const tmp = require(path.join(__dirname, dir)).default; // eslint-disable-line
resolvers = merge(resolvers, tmp.resolvers);
typeDefs.push(tmp.types);
});
export default makeExecutableSchema({
typeDefs,
resolvers
});
view raw index.js hosted with ❤ by GitHub

This is what’s happening on this file:

  1. After importing the needed dependencies, we create the basic types we’ll be using to access the data. The Query type will be used to query data, and the Mutation type will be used to alter data. These will be the base building blocks for the rest of our schema, and will be extended by the rest of the elements on our schema.
  2. Within the Query type we define a query called status that will return a String.
  3. We then define the resolvers variable. Here is where we define how the queries/mutations will be handled by our app. Since we defined a status query inside our Query type, we need to define a resolver to handle that query and we make it return an “ok” string always.
  4. Then we need to collect all the type definitions on our schema. In this file we define just two: Query and Mutation. But we will have a few more (and you’ll have many more in a real application). So what we do is automate the type definition loading process using the node fs module to read the subdirectories within our current dir. We will assume that all the schema elements will be defined within folders and will have an index.js file that exports two elements: resolvers and types.
  5. Finally we build the schema using the makeExecutableSchema utility by passing the type defs and the resolvers.

Creating our Book schema

To make things clear, we’ll build the schema for a Book object and show how type defs and resolvers work with a specific example.

Let’s create a book folder inside our schema dir. Inside we’ll create 5 files:

  • _input.js: We’ll define here all the “input” types. Input types are used as arguments for Mutations.
  • _mutation.js: We’ll define here the mutations and the associated resolvers to each mutation type.
  • _query.js: Similar to mutations, but for queries.
  • _type.js: Here we’ll define our specific type, in this case the Book type, and any resolvers for the type (if any).
  • index.js will read from the previous 4 files and export the typedefs and resolvers.

Let’s see how these files boilerplate looks like:

_input.js:

const Input = '';
export default () => [Input];
view raw _input.js hosted with ❤ by GitHub

_mutation.js:

const Mutation = '';
// `
// extend type Mutation {
// }
// `;
export const mutationTypes = () => [Mutation];
export const mutationResolvers = {
};
view raw _mutation.js hosted with ❤ by GitHub

We’re not defining mutations on this example, but we leave you the boilerplate showing you how to define one. Same goes for inputs.

_query.js:

const Query = `
extend type Query {
books: [Book]
}
`;
export const queryTypes = () => [Query];
export const queryResolvers = {
Query: {
books: () => ([
{
title: "Harry Potter and the Sorcerer's stone",
author: 'J.K. Rowling',
},
{
title: 'Jurassic Park',
author: 'Michael Crichton',
},
])
}
};
view raw _query.js hosted with ❤ by GitHub

We define a single query called books and define the resolver for it. In this case to keep things simple we return a static list of books, but in a real world application you’d connect to a database to retrieve that list.

_type.js:

const Book = `
type Book {
title: String!
author: String!
}
`;
export const types = () => [Book];
export const typeResolvers = {
};
view raw _type.js hosted with ❤ by GitHub

Our Book type has two fields which are required: title and author.

index.js:

import { types, typeResolvers } from './_type';
import { queryTypes, queryResolvers } from './_query';
import inputTypes from './_input';
import { mutationTypes, mutationResolvers } from './_mutation';
export default {
types: () => [types, queryTypes, inputTypes, mutationTypes],
resolvers: Object.assign(queryResolvers, mutationResolvers, typeResolvers),
};
view raw schema-index.js hosted with ❤ by GitHub

As explained before, we use the index.js file to import all the things we defined in the _xxxxx files and export them with a format that is known to our automated schema building process.

You probably noticed that we return types wrapped in a function that returns an array. This allows apollo to avoid duplicating types in the final big schema definition. More about this in the Schemas page of Apollo.

We are set to integrate apollo into our API. We have all the type definitions set, all we have to do now is “connect” Apollo to our express server.

Connecting Apollo to our Express server

We’ll head over to our server.js file and import both the graphql configuration for our app and the main config file.

// GraphQL - Apollo
import apollo from './graphql';

// Config
import config from './config';

Next we make sure that the app port is taken from the app config instead of being hardcoded:

function listen() {
const port = app.get('port') || config.port;
app.listen(port, () => {
console.log(`The server is running and listening at http://localhost:${port}`);
});
}
app.use(cors({
origin: config.corsDomain, // Be sure to switch to your production domain
optionsSuccessStatus: 200
}));
view raw usgin-config.js hosted with ❤ by GitHub

Next, after our status endpoint definition, with a single line of code we’ll integrate all the Apollo code into our express app by doing:

// Append apollo to our API
apollo(app);

In the end, our server.js file will look like this:

// HTTP SERVER
import express from 'express';
import cors from 'cors';
// GraphQL - Apollo
import apollo from './graphql';
// Config
import config from './config';
const app = express();
function setPort(port = 5000) {
app.set('port', parseInt(port, 10));
}
function listen() {
const port = app.get('port') || config.port;
app.listen(port, () => {
console.log(`The server is running and listening at http://localhost:${port}`);
});
}
app.use(cors({
origin: config.corsDomain, // Be sure to switch to your production domain
optionsSuccessStatus: 200
}));
// Endpoint to check if the API is running
app.get('/api/status', (req, res) => {
res.send({ status: 'ok' });
});
// Append apollo to our API
apollo(app);
export default {
getApp: () => app,
setPort,
listen
};
view raw server.js hosted with ❤ by GitHub

Now you can run the project again and open up the GraphiQL interface by opening the http://localhost:5000/api/graphiql URL in your browser.

You can then run any queries or mutations in that pretty nice interface, as well as navigate the schema definition on the right side panel. So go ahead and use this as a query:

query {
  books {
    title
    author
  }
}

This should return the list of books and for each book it’s title and author:

And we’re done! We built a pretty simple GraphQL API using Node.js :) Now you have a basic knowledge on how to do it and you’re ready to move forward and start building your next app’s API using GraphQL to get all the advantages it has over RESTful APIs. It has some disadvantages too, for more info about the comparison you can surf the internet or read this nice post about it.

There are other interesting articles around the web, we found this one by Toptal's team a very good one to get a better understanding of GraphQL and Node.js working together.

Thanks for reading us again and if you liked it share the article on Twitter and follow us!

Share this article

Comment on Medium.com