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
expressMiddlewareadapter. - 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 callingexpressMiddleware— this is a required step.
