This article was originally written for DigitalOcean.

Introduction

In An Introduction to GraphQL, you learned that GraphQL is an open-source query language and runtime for APIs created to solve issues that are often experienced with traditional REST API systems.

A good way to begin understanding how all the pieces of GraphQL fit together is to make a GraphQL API server. Although Apollo GraphQL is a popular commercial GraphQL implementation favored by many large companies, it is not a prerequisite for making your own GraphQL API server.

In this tutorial, you will make an Express API server in Node.js that serves up a GraphQL endpoint. You will also build a GraphQL schema based on the GraphQL type system, including operations, such as queries and mutations, and resolver functions to generate responses for any requests. You will also use the GraphiQL integrated development environment (IDE) to explore and debug your schema and query the GraphQL API from a client.

Prerequisites

To follow this tutorial, you will need:

Setting Up an Express HTTP Server

The first step is to set up an Express server, which you can do before writing any GraphQL code.

In a new project, you will install express and cors with the npm install command:

npm install express cors

Express will be the framework for your server. It is a web application framework for Node.js designed for building APIs. The CORS package, which is Cross-Origin Resource Sharing middleware, will allow you to easily access this server from a browser.

You can also install Nodemon as a dev dependency:

npm install -D nodemon

Nodemon is a tool that helps develop Node-based applications by automatically restarting the application when file changes in the directory are detected.

Installing these packages will have created node_modules and package.json with two dependencies and one dev dependency listed.

Using nano or your favorite text editor, open package.json for editing, which will look something like this:

package.json
{
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.17.3"
  },
  "devDependencies": {
    "nodemon": "^2.0.15"
  }
}

There are a few more fields you will add at this point. To package.json, make the following highlighted changes:

package.json
{
  "main": "server.js",
  "scripts": {
    "dev": "nodemon server.js"
  },
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.17.3"
  },
  "devDependencies": {
    "nodemon": "^2.0.15"
  },
  "type": "module"
}

You will be creating a file for the server at server.js, so you make main point to server.js. This will ensure that npm start starts the server.

To make it easier to develop on the server, you also create a script called "dev" that will run nodemon server.js.

Finally, you add a type of module to ensure you can use import statements throughout the code instead of using the default CommonJS require.

Save and close the file when you're done.

Next, create a file called server.js. In it, you will create a simple Express server, listen on port 4000, and send a request saying Hello, GraphQL!. To set this up, add the following lines to your new file:

server.js
import express from 'express'
import cors from 'cors'

const app = express()
const port = 4000

app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

app.get('/', (request, response) => {
  response.send('Hello, GraphQL!')
})

app.listen(port, () => {
  console.log(`Running a server at http://localhost:${port}`)
})

This code block creates a basic HTTP server with Express. By invoking the express function, you create an Express application. After setting up a few essential settings for CORS and JSON, you will define what should be sent with a GET request to the root (/) using app.get('/'). Finally, use app.listen() to define the port the API server should be listening on.

Save and close the file when you're done.

Now you can run the command to start the Node server:

npm run dev

If you visit http://localhost:4000 in a browser or run a curl http://localhost:4000 command, you will see it return Hello, GraphQL!, indicating that the Express server is running. At this point, you can begin adding code to serve up a GraphQL endpoint.

Setting Up GraphQL HTTP Server Middleware

In this section, you will begin integrating the GraphQL schema into the basic Express server. You will do so by defining a schema, resolvers, and connecting to a data store.

To begin integrating GraphQL into the Express server, you will install three packages: graphql, express-graphql, and @graphql-tools/schema. Run the following command:

npm install graphql@14 express-graphql @graphql-tools/schema
  • graphql: the JavaScript reference implementation for GraphQL.
  • express-graphql: HTTP server middleware for GraphQL.
  • @graphql-tools/schema: a set of utilities for faster GraphQL development.

You can import these packages in the server.js file by adding the highlighted lines:

server.js
import express from 'express'
import cors from 'cors'
import { graphqlHTTP } from 'express-graphql'
import { makeExecutableSchema } from '@graphql-tools/schema'

...

The next step is to create an executable GraphQL schema.

To avoid the overhead of setting up a database, you can use an in-memory store for the data the GraphQL server will query. You can create a data object with the values your database would have. Add the highlighted lines to your file:

server.js
import express from 'express'
import cors from 'cors'
import { graphqlHTTP } from 'express-graphql'
import { makeExecutableSchema } from '@graphql-tools/schema'

const data = {
  warriors: [
    { id: '001', name: 'Jaime' },
    { id: '002', name: 'Jorah' },
  ],
}

...

The data structure here represents a database table called warriors that has two rows, represented by the Jaime and Jorah entries.

Note: Using a real data store is outside of the scope of this tutorial. Accessing and manipulating data in a GraphQL server is performed through the reducers. This can be done by manually connecting to the database, through an ORM like Prisma. Asynchronous resolvers make this possible through the context of a resolver. For the rest of this tutorial, we will use the data variable to represent datastore values.

With your packages installed and some data in place, you will now create a schema, which defines the API by describing the data available to be queried.

GraphQL Schema

Now that you have some basic data, you can begin making a rudimentary schema for an API to get the minimum amount of code necessary to begin using a GraphQL endpoint. This schema is intended to replicate something that might be used for a fantasy RPG game, in which there are characters who have roles such as warriors, wizards, and healers. This example is meant to be open-ended so you can add as much or as little as you want, such as spells and weapons.

A GraphQL schema relies on a type system. There are some built-in types, and you can also create your own type. For this example, you will create a new type called Warrior, and give it two fields: id and name.

type Warrior {
  id: ID!
  name: String!
}

The id has an ID type, and the name has a String type. These are both built-in scalars, or primitive types. The exclamation point (!) means the field is non-nullable, and a value will be required for any instance of this type.

The only additional piece of information you need to get started is a base Query type, which is the entry point to the GraphQL query. We will define warriors as an array of Warrior types.

type Query {
  warriors: [Warrior]
}

With these two types, you have a valid schema that can be used in the GraphQL HTTP middleware. Ultimately, the schema you define here will be passed into the makeExecutableSchema function provided by graphql-tools as typeDefs. The two properties passed into an object on the makeExecutableSchema function will be as follows:

  • typeDefs: a GraphQL schema language string.
  • resolvers: functions that are called to execute a field and produce a value.

In server.js, after importing the dependencies, create a typeDefs variable and assign the GraphQL schema as a string, as shown here:

server.js
...

const data = {
  warriors: [
    { id: '001', name: 'Jaime' },
    { id: '002', name: 'Jorah' },
  ],
}

const typeDefs = `
type Warrior {
  id: ID!
  name: String!
}

type Query {
  warriors: [Warrior]
}
`

...

Now you have your data set as well as your schema defined, as data and typeDefs, respectively. Next, you'll create resolvers so the API knows what to do with incoming requests.

GraphQL Resolver Functions

Resolvers are a collection of functions that generate a response for the GraphQL server. Each resolver function has four parameters:

  • obj: The parent object, which is not necessary to use here since it is already the root, or top-level object.
  • args: Any GraphQL arguments provided to the field.
  • context: State shared between all resolvers, often a database connection.
  • info: Additional information.

In this case, you will make a resolver for the root Query type and return a value for warriors.

To get started with this example server, pass the in-memory data store from earlier in this section by adding the highlighted lines to server.js:

server.js
...

const typeDefs = `
type Warrior {
  id: ID!
  name: String!
}

type Query {
  warriors: [Warrior]
}
`

const resolvers = {
  Query: {
    warriors: (obj, args, context, info) => context.warriors,
  },
}

...

The entry point into the GraphQL server will be through the root Query type on the resolvers. You have now added one resolver function, called warriors, which will return warriors from context. context is where your database entry point will be contained, and for this specific implementation, it will be the data variable that contains your in-memory data store.

Each individual resolver function has four parameters: obj, args, context, and info. The most useful and relevant parameter to our schema right now is context, which is an object shared by the resolvers. It is often used as the connection between the GraphQL server and a database.

Finally, with the typeDefs and resolvers all set, you have enough information to create an executable schema. Add the highlighted lines to your file:

server.js
...

const resolvers = {
  Query: {
    warriors: (obj, args, context, info) => context.warriors,
  },
}

const executableSchema = makeExecutableSchema({
  typeDefs,
  resolvers,
})

...

The makeExecutableSchema function creates a complete schema that you can pass into the GraphQL endpoint.

Now replace the default root endpoint that is currently returning Hello, GraphQL! with the following /graphql endpoint by adding the highlighted lines:

server.js
...

const executableSchema = makeExecutableSchema({
  typeDefs,
  resolvers,
})

app.use(
  '/graphql',
  graphqlHTTP({
    schema: executableSchema,
    context: data,
    graphiql: true,
  })
)

...

The convention is that a GraphQL server will use the /graphql endpoint. Using the graphqlHTTP middleware requires passing in the schema and a context, which in this case, is your mock data store.

You now have everything necessary to begin serving the endpoint. Your server.js code should look like this:

server.js
import express from 'express'
import cors from 'cors'
import { graphqlHTTP } from 'express-graphql'
import { makeExecutableSchema } from '@graphql-tools/schema'

const app = express()
const port = 4000

// In-memory data store
const data = {
  warriors: [
    { id: '001', name: 'Jaime' },
    { id: '002', name: 'Jorah' },
  ],
}

// Schema
const typeDefs = `
type Warrior {
  id: ID!
  name: String!
}

type Query {
  warriors: [Warrior]
}
`

// Resolver for warriors
const resolvers = {
  Query: {
    warriors: (obj, args, context) => context.warriors,
  },
}

const executableSchema = makeExecutableSchema({
  typeDefs,
  resolvers,
})

app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// Entrypoint
app.use(
  '/graphql',
  graphqlHTTP({
    schema: executableSchema,
    context: data,
    graphiql: true,
  })
)

app.listen(port, () => {
  console.log(`Running a server at http://localhost:${port}`)
})

Save and close the file when you're done.

Now you should be able to go to http://localhost:4000/graphql and explore your schema using the GraphiQL IDE.

Your GraphQL API is now complete based on the schema and resolvers you created in this section. In the next section, you'll use the GraphiQL IDE to help you debug and understand your schema.

Using the GraphiQL IDE

Since you applied the graphiql option as true to the GraphQL middleware, you have access to the GraphiQL integrated development environment (IDE). If you visited the GraphQL endpoint in a browser window, you'll find yourself in GraphiQL.

GraphiQL is an in-browser tool for writing, validating, and testing GraphQL queries. Now you can test out your GraphQL server to ensure it's returning the correct data.

Make a query for warriors, requesting the id and name properties. In your browser, add the following lines to the left pane of GraphiQL:

{
  warriors {
    id
    name
  }
}

Submit the query by pressing the Play arrow on the top left, and you should see the return value in JSON on the right-hand side:

{
  "data": {
    "warriors": [
      { "id": "001", "name": "Jaime" },
      { "id": "002", "name": "Jorah" }
    ]
  }
}

If you remove one of the fields in the query, you will see the return value change accordingly. For example, if you only want to retrieve the name field, you can write the query like this:

{
  warriors {
    name
  }
}

And now your response will look like this:

{
  "data": {
    "warriors": [{ "name": "Jaime" }, { "name": "Jorah" }]
  }
}

The ability to query only the fields you need is one of the powerful aspects of GraphQL and is what makes it a client-driven language.

Back in GraphiQL, if you click on Docs all the way to the right, it will expand a sidebar labeled Documentation Explorer. From that sidebar, you can click through the documentation to view your schema in more detail.

Now your API is complete and you've explored how to use it from GraphiQL. The next step will be to make actual requests from a client to your GraphQL API.

Querying the GraphQL API from a Client

Just like with REST APIs, a client can communicate with a GraphQL API by making HTTP requests over the network. Since you can use built-in browser APIs like fetch to make network requests, you can also use fetch to query GraphQL.

For a very basic example, create an HTML skeleton in an index.html file with a <pre> tag:

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>GraphQL Client</title>
  </head>

  <pre><!-- data will be displayed here --></pre>

  <body>
    <script>
      // Add query here
    </script>
  </body>
</html>

In the script tag, make an asynchronous function that sends a POST request to the GraphQL API:

index.html
...
<body>
    <script>
      async function queryGraphQLServer() {
        const response = await fetch('http://localhost:4000/graphql', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            query: '{ warriors { name } }',
          }),
        })
        const data = await response.json()

        // Append data to the pre tag
        const pre = document.querySelector('pre')
        pre.textContent = JSON.stringify(data, null, 2) // Pretty-print the JSON
      }

      queryGraphQLServer()
    </script>
  </body>
...

The Content-Type header must be set to application/json, and the query must be passed in the body as a string. The script will call the function to make the request, and set the response in the pre tag.

Here is the full index.html code.

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>GraphQL</title>
  </head>

  <pre></pre>

  <body>
    <script>
      async function queryGraphQLServer() {
        const response = await fetch('http://localhost:4000/graphql', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            query: '{ warriors { name } }',
          }),
        })
        const data = await response.json()

        const pre = document.querySelector('pre')
        pre.textContent = JSON.stringify(data, null, 2) // Pretty-print the JSON
      }

      queryGraphQLServer()
    </script>
  </body>
</html>

Save and close the file when you're done.

Now when you view the index.html file in a browser, you will see an outgoing network request to the http://localhost:4000/graphql endpoint, which will return a 200 with the data. You can view this network request by opening Developer Tools and navigating to the Network tab.

If your request went through and you got a 200 response with the data from the GraphQL API, congratulations! You made your first GraphQL API server.

Conclusion

In this tutorial, you made a GraphQL API server using the Express framework in Node.js. The GraphQL server consists of a single /graphql endpoint that can handle incoming requests to query the data store. Your API had a schema with the base Query type, a custom Warrior type, and a resolver to fetch the proper data for those types.

Hopefully, this article helped demystify GraphQL and opens up new ideas and possibilities of what can be accomplished with GraphQL. Many tools exist that can help with the more complex aspects of working with GraphQL, such as authentication, security, and caching, but learning how to set up an API server in the simplest way possible should help you understand the essentials of GraphQL.

This tutorial is part of our How To Manage Data with GraphQL series, which covers the basics of using GraphQL.