Build a GraphQL Server with Node.js and Express

Sharukhan PatanSharukhan Patan
4 min read

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.

0
Subscribe to my newsletter

Read articles from Sharukhan Patan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sharukhan Patan
Sharukhan Patan