How To Set Up a GraphQL API Server in Node.js
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:
- A local Node.js environment, which you can set up by following the How To Install Node.js and Create a Local Development Environment tutorial for your operating system and distribution.
- An understanding of the fundamental concepts of GraphQL, which you can find in the tutorial, An Introduction to GraphQL.
- Familiarity with HTTP.
- A basic knowledge of HTML and JavaScript, which you can gain from the series, How To Build a Website With HTML and How To Code in JavaScript.
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:
{
"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:
{
"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:
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:
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:
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 thedata
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:
...
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
:
...
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:
...
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:
...
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:
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:
<!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:
...
<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.
<!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.
This article was originally written for DigitalOcean.
Comments