GraphQL with Node.js and Express

Express is the most widely used Node.js web framework. Integrating GraphQL with Express gives you full control over middleware, routing, authentication, file uploads, and REST endpoints — all running alongside your GraphQL API on the same server.

Why Use Express with GraphQL

  Standalone Apollo Server          Apollo Server + Express
  ──────────────────────────        ──────────────────────────
  Simple to set up                  Full middleware control
  GraphQL only                      REST + GraphQL on same port
  Limited customization             Custom authentication middleware
  Good for GraphQL-only projects    Session management (cookies)
                                    File uploads (Multer)
                                    Rate limiting per route

Installation

  npm install @apollo/server @as-integrations/express4
             express graphql cors body-parser

Setting Up the Express + Apollo Server

  import express              from 'express';
  import cors                 from 'cors';
  import bodyParser           from 'body-parser';
  import { ApolloServer }     from '@apollo/server';
  import { expressMiddleware } from '@as-integrations/express4';

  const app = express();

  // Apollo Server
  const server = new ApolloServer({ typeDefs, resolvers });
  await server.start();

  // Middleware
  app.use(cors());
  app.use(bodyParser.json());

  // Mount GraphQL on /graphql
  app.use(
    '/graphql',
    expressMiddleware(server, {
      context: async ({ req }) => ({
        user: await getUserFromHeader(req.headers.authorization),
        db:   dbPool,
      })
    })
  );

  // Regular REST endpoint alongside GraphQL
  app.get('/health', (req, res) => {
    res.json({ status: 'ok' });
  });

  app.listen(4000, () => {
    console.log('Server running on http://localhost:4000');
    console.log('GraphQL at  http://localhost:4000/graphql');
  });

Adding Authentication Middleware

Express middleware runs before the GraphQL handler. You can verify tokens, set up sessions, or attach user data to req before it reaches the GraphQL context function.

  import jwt from 'jsonwebtoken';

  // Middleware runs on every request
  app.use((req, res, next) => {
    const token = req.headers.authorization?.replace('Bearer ', '');
    if (token) {
      try {
        req.user = jwt.verify(token, process.env.JWT_SECRET);
      } catch {
        req.user = null;
      }
    }
    next();
  });

  // Context reads req.user set by the middleware above
  expressMiddleware(server, {
    context: async ({ req }) => ({
      user: req.user,
      db:   dbPool,
    })
  });

Handling File Uploads

  npm install graphql-upload multer

  import multer from 'multer';
  const upload = multer({ dest: 'uploads/' });

  // REST endpoint for file upload (simpler than GraphQL uploads)
  app.post('/upload', upload.single('file'), (req, res) => {
    res.json({ filename: req.file.filename });
  });

  // Client calls REST for the upload, then GraphQL to link
  // the filename to a user profile or post

Running REST and GraphQL Side by Side

  Server routes:
  ───────────────
  POST /graphql        ← All GraphQL operations
  GET  /health         ← Health check (load balancer)
  POST /upload         ← File uploads (Multer)
  GET  /webhook/stripe ← Stripe payment webhook (REST)
  GET  /oauth/callback ← OAuth callback (session-based)

  All share the same Express app, port, and middleware stack.

Logging GraphQL Operations with Middleware

  app.use('/graphql', (req, res, next) => {
    const operationName = req.body?.operationName || 'anonymous';
    console.log(`[GraphQL] ${operationName} from ${req.ip}`);
    next();
  }, expressMiddleware(server, { context }));

Key Points

  • Express and Apollo Server work together through the expressMiddleware adapter.
  • Express middleware runs before GraphQL, letting you handle auth, logging, and rate limiting conventionally.
  • REST endpoints and GraphQL coexist on the same Express server and the same port.
  • File uploads are easier to handle through a dedicated REST route with Multer than through GraphQL multipart uploads.
  • Call server.start() before calling expressMiddleware — this is a required step.

Leave a Comment