
GraphQL is a query language and runtime that provides an alternative to REST APIs for fetching and manipulating data. It was developed by Facebook and released as an open-source project in 2015. With GraphQL, clients can specify exactly what data they need, and the server responds with only that data, reducing overfetching and underfetching of data.
- Set up a GraphQL Server in Node.js
- Define GraphQL Schemas and Resolvers
- Query Data with GraphQL
- Mutate Data with GraphQL
- Handle Errors in GraphQL
- Debug and Test GraphQL Applications
In this tutorial, we will explore how to work with GraphQL in Node.js, one of the most popular server-side JavaScript environments. We will cover how to set up a GraphQL server, define schemas and resolvers, query and mutate data, handle errors, and implement authentication and authorization. Additionally, we will learn how to integrate GraphQL with existing APIs, use subscriptions for real-time updates, optimize performance, and debug and test GraphQL applications. By the end of this tutorial, you will have a solid understanding of how to use GraphQL in Node.js to build powerful and efficient APIs.
Set up a GraphQL Server in Node.js
To set up a GraphQL server in Node.js, we will use the graphql
and express-graphql
packages. The graphql
package provides the functionality for defining schemas, executing queries, and handling errors, while express-graphql
provides middleware for integrating GraphQL with an Express.js server.
- Create a new Node.js project by running
npm init
in your terminal and following the prompts. - Install the required packages by running
npm install graphql express express-graphql
in your terminal. - Create an
index.js
file and import the required packages:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');
- Define a schema using the
buildSchema
function from thegraphql
package. This schema defines the types of data that can be queried and mutated in the GraphQL API. For example:
const schema = buildSchema(`
type Query {
hello: String
}
`);
This schema defines a Query
type with a single field hello
that returns a String
.
- Create a root resolver object that defines how to resolve the queries and mutations in the schema. For example:
const root = {
hello: () => {
return 'Hello World!';
}
};
This resolver defines a function that returns the string ‘Hello World!’ when the hello
query is executed.
- Create an Express.js server and use the
express-graphql
middleware to handle GraphQL requests. For example:
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(4000, () => {
console.log('Running a GraphQL API server at http://localhost:4000/graphql');
});
This code creates an Express.js server that listens on port 4000 and handles GraphQL requests at the /graphql
endpoint. The graphqlHTTP
middleware is used to handle GraphQL requests and is configured with the schema and root resolver object. The graphiql
option enables the GraphiQL IDE for testing the GraphQL API.
- Start the server by running
node index.js
in your terminal. You should see the message ‘Running a GraphQL API server at http://localhost:4000/graphql‘. - Open a web browser and navigate to
http://localhost:4000/graphql
. You should see the GraphiQL IDE where you can execute thehello
query by typing the following into the query editor:
{
hello
}
- Press the play button to execute the query. You should see the response:
{
"data": {
"hello": "Hello World!"
}
}
You have successfully set up a GraphQL server in Node.js. You can now define more complex schemas and resolvers and build powerful APIs using GraphQL.
Define GraphQL Schemas and Resolvers
In GraphQL, a schema is a blueprint that defines the types of data that can be queried and mutated in the API. A resolver is a function that determines how to resolve a query or mutation in the schema. In this section, we will learn how to define GraphQL schemas and resolvers in Node.js.
- Define a schema using the
buildSchema
function from thegraphql
package. For example:
const { buildSchema } = require('graphql');
const schema = buildSchema(`
type Query {
hello: String
}
type Mutation {
updateMessage(message: String!): String
}
`);
This schema defines a Query
type with a single field hello
that returns a String
, and a Mutation
type with a single field updateMessage
that takes a String
argument message
and returns a String
.
- Create a root resolver object that defines how to resolve the queries and mutations in the schema. For example:
const root = {
hello: () => {
return 'Hello World!';
},
updateMessage: ({ message }) => {
return message;
},
};
This resolver defines a function that returns the string ‘Hello World!’ when the hello
query is executed, and a function that returns the message
argument when the updateMessage
mutation is executed.
- Define a resolver for a field that requires data from a data source. For example, suppose we have a
User
type in the schema:
const schema = buildSchema(`
type Query {
user(id: ID!): User
}
type User {
id: ID
name: String
email: String
}
`);
We can define a resolver for the user
field that fetches the user data from a database or API:
const root = {
user: ({ id }) => {
return getUserById(id);
},
};
This resolver defines a function that takes an id
argument and returns the user data with the corresponding id
from the data source.
- Define a resolver for a field that requires data from a related field. For example, suppose we have a
Post
type in the schema that has aUser
field:
const schema = buildSchema(`
type Query {
post(id: ID!): Post
}
type Post {
id: ID
title: String
content: String
author: User
}
type User {
id: ID
name: String
email: String
}
`);
We can define a resolver for the author
field that fetches the user data for the author of the post:
const root = {
post: ({ id }) => {
return getPostById(id);
},
author: ({ authorId }) => {
return getUserById(authorId);
},
};
This resolver defines a function that takes an authorId
argument and returns the user data with the corresponding id
from the data source.
- Return data in the resolver function. The resolver function can return a value, a promise that resolves to a value, or throw an error. For example:
const root = {
hello: () => {
return 'Hello World!';
},
updateMessage: ({ message }) => {
return message;
},
user: ({ id }) => {
return getUserById(id);
},
post: ({ id }) => {
return getPostById(id);
},
};
- Define input types and use them as arguments for mutations. Input types are used to define complex input arguments for mutations. For example, suppose we have a
Message
type in the schema:
const schema = buildSchema(`
type Query {
message(id: ID!): Message
}
type Mutation {
updateMessage(input: UpdateMessageInput!): Message
}
type Message {
id: ID
text: String
author: String
}
input UpdateMessageInput {
id: ID!
text: String!
}
`);
We can define a resolver for the updateMessage
mutation that takes an input argument of type UpdateMessageInput
and updates the message data in a database or API:
const root = {
message: ({ id }) => {
return getMessageById(id);
},
updateMessage: ({ input }) => {
const { id, text } = input;
return updateMessageById(id, { text });
},
};
This resolver defines a function that takes an input
argument of type UpdateMessageInput
and updates the message data with the corresponding id
in the data source.
- Use context to provide data to resolvers. Context is a way to pass data to resolvers that is shared across all resolvers in a GraphQL operation. For example, suppose we have an authentication token that needs to be passed to all resolvers:
const root = {
message: ({ id }, context) => {
const { token } = context;
return getMessageById(id, token);
},
updateMessage: ({ input }, context) => {
const { id, text } = input;
const { token } = context;
return updateMessageById(id, { text }, token);
},
};
This resolver uses the context
argument to pass the token
data to the resolvers.
Query Data with GraphQL
In GraphQL, queries are used to fetch data from the API. In this section, we will learn how to query data with GraphQL in Node.js.
- Define a schema using the
buildSchema
function from thegraphql
package. For example:
const { buildSchema } = require('graphql');
const schema = buildSchema(`
type Query {
hello: String
message(id: ID!): Message
messages: [Message]
}
type Message {
id: ID
text: String
author: String
}
`);
This schema defines a Query
type with three fields: hello
, which returns a String
; message
, which takes an id
argument and returns a single Message
object; and messages
, which returns an array of Message
objects.
- Create a root resolver object that defines how to resolve the queries in the schema. For example:
const root = {
hello: () => {
return 'Hello World!';
},
message: ({ id }) => {
return getMessageById(id);
},
messages: () => {
return getAllMessages();
},
};
This resolver defines functions that return data for the hello
, message
, and messages
queries. The getMessageById
function fetches a single message by its id
, while the getAllMessages
function fetches all messages.
- Use the
graphql
function from thegraphql
package to execute a query. For example:
const { graphql } = require('graphql');
const query = `
query {
hello
message(id: "1") {
text
author
}
messages {
id
text
}
}
`;
graphql(schema, query, root).then((result) => {
console.log(result.data);
});
This code defines a query that fetches the hello
field, the text
and author
fields for a single message with id
of 1, and the id
and text
fields for all messages. The graphql
function executes the query using the schema and root resolver object and logs the response data to the console.
- Use variables to pass arguments to a query. Variables are used to pass arguments to a query dynamically. For example, suppose we want to fetch a message with an
id
that is determined at runtime:
const { graphql } = require('graphql');
const query = `
query GetMessageById($id: ID!) {
message(id: $id) {
text
author
}
}
`;
const variables = { id: '1' };
graphql(schema, query, root, null, variables).then((result) => {
console.log(result.data);
});
This code defines a query that fetches a message with an id
that is passed as a variable. The graphql
function executes the query using the schema, root resolver object, and variables and logs the response data to the console.
- Use aliases to rename fields in a query. Aliases are used to rename fields in a query to avoid naming conflicts. For example, suppose we want to fetch the text of a message and also the author’s name:
const { graphql } = require('graphql');
const query = `
query GetAuthorAndText {
message(id: "1") {
authorName: author
text
}
}
`;
graphql(schema, query, root).then((result) => {
console.log(result.data);
});
This code defines a query that fetch the author
field from a message object and renames it to authorName
. The graphql
function executes the query using the schema and root resolver object and logs the response data to the console.
- Use fragments to reuse fields in a query. Fragments are used to reuse fields in a query to avoid duplication. For example, suppose we want to fetch the
id
andtext
fields for a message and also for a user:
const { graphql } = require('graphql');
const query = `
query GetMessageAndUser {
message(id: "1") {
...MessageFields
}
user(id: "1") {
...UserFields
}
}
fragment MessageFields on Message {
id
text
}
fragment UserFields on User {
id
name
}
`;
graphql(schema, query, root).then((result) => {
console.log(result.data);
});
This code defines a query that fetches the id
and text
fields for a message and also for a user, and uses fragments to avoid duplicating those fields. The graphql
function executes the query using the schema and root resolver object and logs the response data to the console.
Mutate Data with GraphQL
In GraphQL, mutations are used to modify data in the API. In this section, we will learn how to mutate data with GraphQL in Node.js.
- Define a schema using the
buildSchema
function from thegraphql
package. For example:
const { buildSchema } = require('graphql');
const schema = buildSchema(`
type Query {
hello: String
message(id: ID!): Message
messages: [Message]
}
type Mutation {
createMessage(input: CreateMessageInput!): Message
updateMessage(input: UpdateMessageInput!): Message
deleteMessage(id: ID!): Message
}
type Message {
id: ID
text: String
author: String
}
input CreateMessageInput {
text: String!
author: String!
}
input UpdateMessageInput {
id: ID!
text: String!
}
`);
This schema defines a Mutation
type with three fields: createMessage
, which takes an input argument of type CreateMessageInput
and creates a new Message
object; updateMessage
, which takes an input argument of type UpdateMessageInput
and updates an existing Message
object; and deleteMessage
, which takes an id
argument and deletes a Message
object.
- Create a root resolver object that defines how to resolve the mutations in the schema. For example:
const root = {
createMessage: ({ input }) => {
const { text, author } = input;
const id = createMessage({ text, author });
return getMessageById(id);
},
updateMessage: ({ input }) => {
const { id, text } = input;
updateMessageById(id, { text });
return getMessageById(id);
},
deleteMessage: ({ id }) => {
const message = getMessageById(id);
deleteMessageById(id);
return message;
},
};
This resolver defines functions that create, update, and delete messages. The createMessage
function creates a new message with the given text
and author
and returns the created message. The updateMessage
function updates an existing message with the given id
and text
and returns the updated message. The deleteMessage
function deletes the message with the given id
and returns the deleted message.
- Use the
graphql
function from thegraphql
package to execute a mutation. For example:
const { graphql } = require('graphql');
const mutation = `
mutation CreateMessage($input: CreateMessageInput!) {
createMessage(input: $input) {
id
text
author
}
}
`;
const variables = {
input: {
text: 'Hello World',
author: 'John Doe',
},
};
graphql(schema, mutation, root, null, variables).then((result) => {
console.log(result.data);
});
This code defines a mutation that creates a new message with the given text
and author
. The graphql
function executes the mutation using the schema, root resolver object, and variables and logs the response data to the console.
Handle Errors in GraphQL
In GraphQL, errors can occur when executing queries or mutations. In this section, we will learn how to handle errors in GraphQL in Node.js.
- Define a schema using the
buildSchema
function from thegraphql
package. For example:
const { buildSchema } = require('graphql');
const schema = buildSchema(`
type Query {
hello: String
message(id: ID!): Message
messages: [Message]
}
type Mutation {
createMessage(input: CreateMessageInput!): Message
updateMessage(input: UpdateMessageInput!): Message
deleteMessage(id: ID!): Message
}
type Message {
id: ID
text: String
author: String
}
input CreateMessageInput {
text: String!
author: String!
}
input UpdateMessageInput {
id: ID!
text: String!
}
`);
This schema defines a Mutation
type with three fields: createMessage
, which takes an input argument of type CreateMessageInput
and creates a new Message
object; updateMessage
, which takes an input argument of type UpdateMessageInput
and updates an existing Message
object; and deleteMessage
, which takes an id
argument and deletes a Message
object.
- Create a root resolver object that defines how to resolve the queries and mutations in the schema. For example:
const root = {
hello: () => {
return 'Hello World!';
},
message: ({ id }) => {
const message = getMessageById(id);
if (!message) {
throw new Error(`Message with id ${id} not found`);
}
return message;
},
messages: () => {
return getAllMessages();
},
createMessage: ({ input }) => {
const { text, author } = input;
const id = createMessage({ text, author });
return getMessageById(id);
},
updateMessage: ({ input }) => {
const { id, text } = input;
const message = getMessageById(id);
if (!message) {
throw new Error(`Message with id ${id} not found`);
}
updateMessageById(id, { text });
return getMessageById(id);
},
deleteMessage: ({ id }) => {
const message = getMessageById(id);
if (!message) {
throw new Error(`Message with id ${id} not found`);
}
deleteMessageById(id);
return message;
},
};
This resolver defines functions that handle errors for the queries and mutations. The message
, updateMessage
, and deleteMessage
functions check if the requested message exists and throw an error if it does not. The createMessage
function creates a new message and returns it.
- Use the
graphql
function from thegraphql
package to execute a query or mutation. For example:
const { graphql } = require('graphql');
const query = `
query GetMessageById($id: ID!) {
message(id: $id) {
text
author
}
}
`;
const variables = { id: '1' };
graphql(schema, query, root, null, variables).then((result) => {
console.log(result.data);
}).catch((error) => {
console.log(error);
});
This code defines a query that fetches a message with an id
that is passed as a variable. The graphql
function executes the query using the schema, root resolver object, and variables and logs the response data to the console if there are no errors. If an error occurs, it logs the error to the console.
- Use try-catch blocks to catch errors in synchronous code. If you are working with synchronous code, you can use try-catch blocks to catch errors. For example:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const app = express();
app.use('/graphql', graphqlHTTP({
schema,
rootValue: root,
graphiql: true,
}));
app.listen(3000, () => {
console.log('Server started on port 3000');
});
process.on('uncaughtException', (error) => {
console.log(error);
});
process.on('unhandledRejection', (error) => {
console.log(error);
});
This code sets up an Express server that uses the graphqlHTTP
middleware to handle GraphQL requests. It also sets up event handlers for uncaught exceptions and unhandled rejections that log errors to the console.
- Use async/await and try-catch blocks to catch errors in asynchronous code. If you are working with asynchronous code, you can use async/await and try-catch blocks to catch errors. For example:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const app = express();
app.use('/graphql', async (req, res, next) => {
try {
const { data } = await graphql(schema, req.body.query, rootValue);
res.send(data);
} catch (error) {
next(error);
}
});
app.use((error, req, res, next) => {
console.log(error);
res.status(500).send('Internal Server Error');
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
This code sets up an Express server that uses a custom middleware to handle GraphQL requests. The middleware uses async/await and try-catch blocks to catch errors and handle them appropriately. If an error occurs, it logs the error to the console and sends a 500 Internal Server Error response.
Congratulations! You have successfully learned how to handle errors in GraphQL in Node.js. You can now handle errors in your GraphQL API using a variety of techniques such as try-catch blocks, error handling middleware, and event handlers.
Debug and Test GraphQL Applications
Debugging and testing are critical components of software development. In this section, we will learn how to debug and test GraphQL applications in Node.js.
- Use the
debug
module to debug your application. Thedebug
module is a popular debugging tool for Node.js applications. To use it, add the following line of code to your entry point:
const debug = require('debug')('app');
This line of code creates a new debugging instance named app
. You can use this instance to log debugging messages to the console. For example:
debug('Starting server...');
This code logs a message to the console with the prefix app:
.
- Use the
graphql-tools
package to mock your GraphQL schema for testing. Thegraphql-tools
package provides several tools for working with GraphQL schemas, including a function for mocking a schema. To use it, install thegraphql-tools
package and add the following code to your tests:
const { mockServer } = require('graphql-tools');
const schema = require('./schema');
const mocks = require('./mocks');
describe('My GraphQL API', () => {
it('returns a list of messages', async () => {
const server = mockServer(schema, mocks);
const query = `
query {
messages {
id
text
}
}
`;
const response = await server.query(query);
expect(response.data.messages).toHaveLength(5);
});
});
This code defines a test that uses the mockServer
function to create a mock GraphQL server with the provided schema and mocks. It then executes a query and asserts that the response data contains a list of messages with a length of 5.
- Use the
supertest
package to test your GraphQL API over HTTP. Thesupertest
package provides a convenient way to test HTTP servers in Node.js. To use it, install thesupertest
package and add the following code to your tests:
const request = require('supertest');
const app = require('./app');
describe('My GraphQL API', () => {
it('returns a list of messages', async () => {
const response = await request(app)
.post('/graphql')
.send({
query: `
query {
messages {
id
text
}
}
`
})
.set('Accept', 'application/json');
expect(response.statusCode).toBe(200);
expect(response.body.data.messages).toHaveLength(5);
});
});
This code defines a test that uses the supertest
package to send a GraphQL query to the /graphql
endpoint of the server and assert that the response data contains a list of messages with a length of 5.
- Use a code coverage tool to measure your test coverage. Code coverage is a measure of how much of your code is executed by your tests. There are several code coverage tools available for Node.js applications, including
nyc
andistanbul
. To usenyc
, install thenyc
package and add the following code to yourpackage.json
file:
{
"scripts": {
"test": "nyc --reporter=lcov --reporter=text mocha tests/**/*.js"
}
}
This code sets up a test script that uses nyc
to execute the tests and generate a code coverage report in both LCOV and text formats.
- Getting Started With GraphQL.js | GraphQL (graphql.org)
- A Practical GraphQL Getting Started Guide with Node.js and (www.digitalocean.com)
- How to Get Started with GraphQL and Node.js (www.freecodecamp.org)
- GraphQL with Node.js: A Beginner’s Guide – Medium (blog.devgenius.io)
- Working with GraphQL in Node.js – vegibit (vegibit.com)
- Introduction to the Node.js reference architecture, Part 4: (developers.redhat.com)
- How to Create a Secure Node.js GraphQL API | Toptal® (www.toptal.com)
- How to Build a GraphQL server with NodeJS and Express (medium.com)
- node.js – querying graphql with node – Stack Overflow (stackoverflow.com)
- Building GraphQL APIs With Node – kinsta.com (kinsta.com)
- How to Build a GraphQL server with NodeJS and Express (dev.to)
- Build a secure GraphQL API with Node.js | Okta (developer.okta.com)
- How to build a GraphQL Server Using Node.js – Section (www.section.io)
- Getting started with Node.js, GraphQL, and TypeScript (sudo-user.medium.com)
- Create a GraphQL Application With Node.js, NoSQL Databases (www.couchbase.com)
- #2 API Design & Project Setup | Build a Complete App with GraphQL (www.youtube.com)