Migrate from Express GraphQL to GraphQL over HTTP
When GraphQL was open-sourced in 2015, express-graphql quickly became the standard way to run a GraphQL server in Node.js.
Built as middleware for Express, it offered a simple and reliable development experience. However, it hasn’t received a
feature update since 2018 and is no longer actively maintained. For modern applications, it lacks support for new transport
features, fine-grained request handling, and deployment flexibility.
graphql-http is a lightweight implementation of
the GraphQL over HTTP specification. It’s framework-agnostic, built to be
composable, and easy to integrate into different server environments. Unlike express-graphql, it can run in a wide range of
environments, not just Express.
This guide is for developers currently using express-graphql who want to
modernize their stack, adopt the HTTP spec, or decouple their GraphQL server
from Express.
Benefits of migrating
express-graphql is no longer supported
The library has not received updates for some time. As a deprecated package, it is not evolving with the GraphQL ecosystem. This makes it less flexible for long-term projects.
graphql-http is spec-compliant by default
The GraphQL over HTTP specification defines how GraphQL should be transported over HTTP, including request methods, status codes, content types, and more. graphql-http follows this spec precisely, helping your server behave predictably and remain compatible with future tooling.
It’s framework-agnostic by design
Instead of relying on Express, graphql-http is built on the standard Web Request and Response interfaces. It works with Express, Fastify, Node’s native HTTP server, and can also be used in serverless and edge environments.
It fits into modern JavaScript stacks
graphql-http supports ESM and works well with modern build tools and lightweight deployment platforms. Its composable design makes it easy to customize, wrap, and integrate into different application architectures.
Designed for future compatibility
As GraphQL evolves, tools and platforms increasingly expect spec-compliant behavior. Migration to graphql-http helps ensure your
server will support future capabilities without relying on workarounds.
Understand current limitations
Although graphql-http is a strong foundation for modern GraphQL servers, it’s important to note what it doesn’t include:
- It doesn’t support subscriptions or experimental features like incremental delivery (
@defer/@stream) out of the box. - These limitations are by design.
graphql-httpstrictly adheres to the current GraphQL over HTTP specification, which does not yet define behavior for those features. - If your application needs support for subscriptions or live queries, consider using complementary libraries like
graphql-wsorgraphql-sse.
These are not limitations unique to graphql-http. express-graphql does not support these features either, but it’s important
to set the right expectations about extensibility.
Migration guide
The following steps walk through how to migrate an existing express-graphql server to use graphql-http. The steps assume you already have a working Express app using express-graphql.
Prerequisites
Before you begin, make sure you have:
- Node.js 16 or later
- A GraphQL schema
- An existing Express app configured with
express-graphql
Step 1: Install graphql-http and the Express adapter
Install the core graphql-http package along with its Express adapter:
npm install graphql graphql-httpThe graphql package is a peer dependency of graphql-http, and must be installed if it isn’t already.
Step 2: Remove express-graphql middleware
In your Express server file, remove the express-graphql middleware:
// Before (using express-graphql)
import { graphqlHTTP } from 'express-graphql';
app.use('/graphql', graphqlHTTP({
schema,
graphiql: true,
}));Step 3: Add graphql-http middleware with createHandler
Replace it with the graphql-http handler using the Express adapter:
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
import { schema } from './schema.js';
const app = express();
app.all('/graphql', createHandler({ schema }));
app.listen(4000);- Use
app.all()to allow bothGETandPOSTrequests. - The handler accepts an options object for GraphQL-specific settings like
schema,rootValue, andcontext, but doesn’t handle server-level features such as middleware or request preprocessing likeexpress-graphqldid.
Step 4: Handle context, error formatting, and extensions
You can provide options like context, rootValue, and formatError:
import { GraphQLError } from 'graphql';
app.all('/graphql', createHandler({
schema,
context: async (req, res) => {
const user = await authenticate(req);
return { user };
},
formatError: (error) =>
new GraphQLError(error.message, {
nodes: error.nodes,
path: error.path,
extensions: {
code: 'INTERNAL_SERVER_ERROR',
timestamp: Date.now(),
},
}),
}));contextcan be a static object or an async function.- You can also pass
rootValueor other GraphQL-specific options. - To modify the HTTP response, such as adding headers or extensions, you’ll need to do that outside of graphql-http, using Express middleware or route handlers.
Step 5: Add a GraphQL IDE
Unlike express-graphql, graphql-http does not include a built-in GraphQL IDE. If you want to add one:
-
Use a tool like Ruru to serve an interactive GraphQL UI locally:
npx ruru -SP -p 4001 -e http://localhost:4000/graphql -
Or serve a static HTML page that embeds GraphiQL from a CDN.
In either case, make sure to restrict access in production environments.
Step 6: Test your setup
After migrating, verify that your server responds correctly:
- Send queries and mutations using your preferred client.
- Check for proper HTTP status codes and response shapes.
- Check the GraphQL
contextand related variables are populated correctly and that your dataloaders and authorization logic are functioning as expected.
Best practices
When migrating from express-graphql to graphql-http, there are a few key differences and potential pitfalls to keep in mind. These tips can help you avoid common issues and ensure a smoother transition.
Be aware of different error behavior
graphql-http follows the GraphQL over HTTP spec closely, which means error formatting and status codes may differ from what you’re used to with express-graphql. For example:
- Invalid queries may return a
400 Bad Requestinstead of a200 OK. - Errors in parsing or validation are surfaced earlier and more strictly.
- You can customize error output using the
formatErroroption, but it must conform to the spec.
This can affect client expectations if they were relying on express-graphql’s more lenient defaults.
Watch for framework-specific middleware behavior
Since graphql-http is framework-agnostic, it does not handle things like body parsing, CORS, or compression. You’ll need to ensure those are handled appropriately by your Express setup:
import cors from 'cors';
import express from 'express';
app.use(cors());
app.use(express.json());This gives you more control but requires a bit more setup.
Understand streaming and file upload limitations
graphql-http aims to support the GraphQL over HTTP spec, including eventually supporting response streaming. However,
support for features like @defer and @stream is still evolving. These capabilities are experimental in graphql-js
and not yet supported by graphql-http.
- Some GraphQL clients have begun adding support for multipart responses, but broad adoption is still evolving.
graphql-httpdoes not support streaming features such as@deferor@stream, as these are not part of the current GraphQL over HTTP specification.- If your app relies on incremental delivery, use a transport library like
graphql-sse, but note that it replacesgraphql-httpand must be used as your server handler.
What’s next
graphql-http is the reference implementation of the GraphQL-over-HTTP specification,
but there are many other servers you can use to serve your GraphQL API, each with
different features and trade-offs. For a list of other spec-compliant server
implementations see the
graphql-http server list, and don’t forget to check out the
Tools and Libraries page on graphql.org.