In this chapter, we will explore GraphQL with Node.js in detail. GraphQL is a powerful query language for APIs and a runtime for executing those queries by using a type system that you define for your data. It provides an alternative to RESTful APIs and offers more flexibility and efficiency in how clients request data.
Bonus💡: Practical example at the end of article
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system that you define for your data. Unlike REST, which requires loading data from multiple endpoints, GraphQL allows clients to request exactly the data they need, all in one go.
GraphQL is favored over REST in many scenarios because it:
We will create a simple GraphQL server using Node.js and Express.
mkdir graphql-nodejs
cd graphql-nodejs
npm init -y
2.Install Required Packages: Install express
, express-graphql
, and graphql
:
npm install express express-graphql graphql
graphql-nodejs/
├── index.js
├── schema.js
└── package.json
The schema defines the structure of the API and the types of data that can be queried.
schema.js
// File: schema.js
const { buildSchema } = require('graphql');
// Define the schema
const schema = buildSchema(`
type Query {
hello: String
user(id: Int!): User
users: [User]
}
type Mutation {
createUser(name: String!, age: Int!): User
}
type User {
id: Int
name: String
age: Int
}
`);
let users = [
{ id: 1, name: 'John Doe', age: 28 },
{ id: 2, name: 'Jane Doe', age: 25 }
];
// Define resolvers
const root = {
hello: () => 'Hello, world!',
user: ({ id }) => users.find(user => user.id === id),
users: () => users,
createUser: ({ name, age }) => {
const newUser = { id: users.length + 1, name, age };
users.push(newUser);
return newUser;
}
};
module.exports = { schema, root };
Here, we define a simple schema with a Query
type to fetch data and a Mutation
type to modify data. We also define a User
type, which represents the data structure of a user.
File: index.js
// File: index.js
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { schema, root } = require('./schema');
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true, // Enables the GraphiQL tool
}));
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}/graphql`);
});
Here, we set up an Express server and use the express-graphql
middleware to handle requests to the /graphql
endpoint. We also enable GraphiQL
, a graphical interface for making GraphQL queries.
GraphQL queries are how clients ask for data from the server. In this section, we’ll look at how to write queries to fetch data.
With the server running, navigate to http://localhost:4000/graphql
to open the GraphiQL interface.
Simple Query:
{
hello
}
// Output
{
"data": {
"hello": "Hello, world!"
}
}
2.Querying a Single User:
{
user(id: 1) {
name
age
}
}
// Output
{
"data": {
"user": {
"name": "John Doe",
"age": 28
}
}
}
3.Querying Multiple Users:
{
users {
id
name
age
}
}
// Output
{
"data": {
"users": [
{
"id": 1,
"name": "John Doe",
"age": 28
},
{
"id": 2,
"name": "Jane Doe",
"age": 25
}
]
}
}
Mutations in GraphQL are used to modify data on the server (similar to POST, PUT, DELETE in REST).
Creating a New User:
mutation {
createUser(name: "Alice", age: 30) {
id
name
age
}
}
// Output
{
"data": {
"createUser": {
"id": 3,
"name": "Alice",
"age": 30
}
}
}
2.Fetching All Users After Mutation:
{
users {
id
name
age
}
}
// Output
{
"data": {
"users": [
{
"id": 1,
"name": "John Doe",
"age": 28
},
{
"id": 2,
"name": "Jane Doe",
"age": 25
},
{
"id": 3,
"name": "Alice",
"age": 30
}
]
}
}
In complex GraphQL mutations, you might need to pass multiple arguments. GraphQL allows you to define input types to structure these arguments.
// Extend the schema.js
const schema = buildSchema(`
input UserInput {
name: String!
age: Int!
}
type Query {
users: [User]
}
type Mutation {
createUser(input: UserInput): User
}
type User {
id: Int
name: String
age: Int
}
`);
mutation {
createUser(input: { name: "Bob", age: 22 }) {
id
name
age
}
}
// Output
{
"data": {
"createUser": {
"id": 4,
"name": "Bob",
"age": 22
}
}
}
GraphQL allows the use of aliases to rename the result of a query or fragments to reuse parts of queries.
Using Aliases:
{
john: user(id: 1) {
name
}
jane: user(id: 2) {
name
}
}
// Output
{
"data": {
"john": {
"name": "John Doe"
},
"jane": {
"name": "Jane Doe"
}
}
}
Using Fragments
{
users {
...userFields
}
}
fragment userFields on User {
id
name
age
}
// Output
{
"data": {
"users": [
{
"id": 1,
"name": "John Doe",
"age": 28
},
{
"id": 2,
"name": "Jane Doe",
"age": 25
}
]
}
}
In real-world applications, you need to protect certain parts of your GraphQL API. This can be done by implementing authentication and authorization.
1 Middleware Function for Authentication:
// Add to schema.js or index.js
const authenticate = (resolver) => {
return (parent, args, context, info) => {
if (!context.isAuthenticated) {
throw new Error('Not Authenticated');
}
return resolver(parent, args, context, info);
};
};
2.Applying Middleware to Resolvers:
// Apply middleware to a resolver
const root = {
users: authenticate(() => users)
};
3.Passing Context:
// In index.js
app.use('/graphql', graphqlHTTP((req) => ({
schema: schema,
rootValue: root,
context: {
isAuthenticated: req.headers.authorization === 'Bearer mysecrettoken'
},
graphiql: true,
})));
Let’s build a practical example of using GraphQL with Node.js by creating a simple Bookstore API. This API will allow us to perform operations like adding books, retrieving books, and managing authors. We’ll cover setting up the project, creating the GraphQL schema, writing queries and mutations, and testing the API.
Book
: Represents a book with fields like title, author, and publication year.Author
: Represents an author with fields like name and age.
mkdir bookstore-graphql
cd bookstore-graphql
npm init -y
2.Install Required Packages: Install express
, express-graphql
, and graphql
:
npm install express express-graphql graphql
bookstore-graphql/
├── index.js
├── schema.js
├── data.js
└── package.json
We’ll create a simple in-memory data store to manage our books and authors.
File: data.js
// File: data.js
const books = [
{ id: 1, title: '1984', authorId: 1, year: 1949 },
{ id: 2, title: 'Brave New World', authorId: 2, year: 1932 }
];
const authors = [
{ id: 1, name: 'George Orwell', age: 46 },
{ id: 2, name: 'Aldous Huxley', age: 69 }
];
module.exports = { books, authors };
This file exports two arrays, books
and authors
, which will act as our in-memory data store.
Next, we’ll define our GraphQL schema, including types, queries, and mutations.
schema.js
// File: schema.js
const { buildSchema } = require('graphql');
const { books, authors } = require('./data');
// Define the GraphQL schema
const schema = buildSchema(`
type Query {
book(id: Int!): Book
books: [Book]
author(id: Int!): Author
authors: [Author]
}
type Mutation {
addBook(title: String!, authorId: Int!, year: Int!): Book
addAuthor(name: String!, age: Int!): Author
}
type Book {
id: Int
title: String
author: Author
year: Int
}
type Author {
id: Int
name: String
age: Int
books: [Book]
}
`);
// Define the resolvers
const root = {
book: ({ id }) => books.find(book => book.id === id),
books: () => books,
author: ({ id }) => authors.find(author => author.id === id),
authors: () => authors,
addBook: ({ title, authorId, year }) => {
const newBook = { id: books.length + 1, title, authorId, year };
books.push(newBook);
return newBook;
},
addAuthor: ({ name, age }) => {
const newAuthor = { id: authors.length + 1, name, age };
authors.push(newAuthor);
return newAuthor;
}
};
module.exports = { schema, root };
Query
type defines the possible queries, such as fetching books and authors.Mutation
type allows us to add new books and authors.Book
type has a title
, author
, and year
.Author
type has a name
, age
, and a list of books
.Now, we’ll set up the Express server and integrate the GraphQL middleware.
index.js
// File: index.js
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { schema, root } = require('./schema');
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true, // Enable GraphiQL UI
}));
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}/graphql`);
});
This sets up a basic Express server that listens on port 4000 and uses the /graphql
endpoint to handle GraphQL requests. The GraphiQL
UI is enabled for easy testing.
Start the Server:
node index.js
Access the GraphQL Playground: Open your browser and go to http://localhost:4000/graphql
. Here you can run queries and mutations using the GraphiQL interface.
Fetch All Books
{
books {
id
title
year
author {
name
age
}
}
}
// Output
{
"data": {
"books": [
{
"id": 1,
"title": "1984",
"year": 1949,
"author": {
"name": "George Orwell",
"age": 46
}
},
{
"id": 2,
"title": "Brave New World",
"year": 1932,
"author": {
"name": "Aldous Huxley",
"age": 69
}
}
]
}
}
Fetch a Single Book by ID
{
book(id: 1) {
title
year
author {
name
age
}
}
}
// Output
{
"data": {
"book": {
"title": "1984",
"year": 1949,
"author": {
"name": "George Orwell",
"age": 46
}
}
}
}
Fetch All Authors
{
authors {
id
name
age
books {
title
}
}
}
// Output
{
"data": {
"authors": [
{
"id": 1,
"name": "George Orwell",
"age": 46,
"books": [
{
"title": "1984"
}
]
},
{
"id": 2,
"name": "Aldous Huxley",
"age": 69,
"books": [
{
"title": "Brave New World"
}
]
}
]
}
}
mutation {
addBook(title: "To Kill a Mockingbird", authorId: 1, year: 1960) {
id
title
year
author {
name
}
}
}
// Output
{
"data": {
"addBook": {
"id": 3,
"title": "To Kill a Mockingbird",
"year": 1960,
"author": {
"name": "George Orwell"
}
}
}
}
mutation {
addAuthor(name: "Harper Lee", age: 89) {
id
name
age
}
}
// Output
{
"data": {
"addAuthor": {
"id": 3,
"name": "Harper Lee",
"age": 89
}
}
}
After adding the new book and author, you can re-run the books
and authors
queries to see the updated data.
This practical example demonstrated how to create a simple Bookstore API using GraphQL with Node.js. We covered:
This chapter has walked you through the fundamentals of GraphQL with Node.js, from setting up a simple server to advanced topics like input types, fragments, and authorization. With GraphQL, you can build flexible, efficient, and scalable APIs that empower clients to request exactly the data they need, in the shape they need it.Happy coding !❤️