Build a GraphQL Server with Node.js and Express

The first time I looked into GraphQL, it felt like I was reading a new language altogether. Not because it was difficult — just... different. Coming from the familiar structure of REST, the idea of sending a single query and getting exactly the data I needed was refreshing.
In this walkthrough, I’ll show you how to set up a basic GraphQL server using Node.js and Express. It's not just about making it work — it's about making it clean, organized, and easy to grow later on. No fluff, just the essentials.
A Quick Look at the Stack
So why GraphQL with Express?
Express keeps things simple — it doesn't get in your way. It lets you control how things are wired up. And GraphQL, well, it eliminates those annoying moments when you hit an endpoint only to get more (or less) data than you actually need.
Pair them up, and you get a lightweight, flexible server that responds precisely to what the frontend asks for. No more over-fetching, no more chasing multiple endpoints.
Folder Setup
Here’s the folder structure I like to follow when building small-to-medium GraphQL projects:
graphql-server/
├── index.js
├── schema/
│ ├── typeDefs.js
│ └── resolvers.js
├── middleware/
│ └── logger.js
├── data/
│ └── books.js
├── package.json
Simple, right? This keeps types, logic, and utilities separate. You’ll thank yourself later when the project grows.
Step 1: Kick Off the Project
mkdir graphql-server
cd graphql-server
npm init -y
Then install what’s needed:
npm install express express-graphql graphql
Step 2: Mock Some Data
To avoid setting up a database just yet, we’ll hardcode a few books in data/books.js
:
const books = [
{ id: '1', title: 'Zero to One', author: 'Peter Thiel' },
{ id: '2', title: 'Atomic Habits', author: 'James Clear' }
];
module.exports = books;
Step 3: Define Types and Queries
Create schema/typeDefs.js
to define the shape of the data and what queries clients can run:
const { buildSchema } = require('graphql');
const schema = buildSchema(`
type Book {
id: ID!
title: String!
author: String!
}
type Query {
allBooks: [Book]
book(id: ID!): Book
}
`);
module.exports = schema;
Step 4: Build the Resolvers
Resolvers are where you define how to respond when a query is made. Create schema/resolvers.js
:
const books = require('../data/books');
const resolvers = {
allBooks: () => books,
book: ({ id }) => books.find(b => b.id === id)
};
module.exports = resolvers;
Step 5: Add a Basic Logger
A little middleware never hurts. In middleware/logger.js
, add this:
const logger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
};
module.exports = logger;
Step 6: Stitch It All Together
Now for the main event: the entry point. Here’s what goes in index.js
:
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const schema = require('./schema/typeDefs');
const resolvers = require('./schema/resolvers');
const logger = require('./middleware/logger');
const app = express();
const PORT = 4000;
app.use(logger);
app.use('/graphql', graphqlHTTP({
schema,
rootValue: resolvers,
graphiql: true
}));
app.listen(PORT, () => {
console.log(`GraphQL server running at http://localhost:${PORT}/graphql`);
});
Step 7: Fire It Up and Test
Start the server:
node index.js
Open a browser and visit: http://localhost:4000/graphql
Try this query:
{
allBooks {
id
title
author
}
}
Or this:
{
book(id: "1") {
title
author
}
}
Final Thoughts
What you’ve got now is a fully functional GraphQL server — with a clean structure, mock data, and a working query system. It’s not production-ready just yet, but it’s the perfect base to build on.
From here, the possibilities open up:
Add mutations to create or update data
Connect to MongoDB or PostgreSQL
Add JWT-based authentication
Use tools like Apollo Server or schema-first libraries
The important part is: you've set it up yourself. You know where each piece lives and how it connects. That’s far more valuable than copying a boilerplate repo.
Next up, we’ll look at how to connect this to a database and handle real data — but for now, take a moment and run a few more queries. Play around with it. That’s the best way to learn.
Subscribe to my newsletter
Read articles from Sharukhan Patan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
