graphql-http vs graphql-yoga vs express-graphql vs apollo-server
GraphQL Server Libraries Comparison
3 Years
graphql-httpgraphql-yogaexpress-graphqlapollo-serverSimilar Packages:
What's GraphQL Server Libraries?

GraphQL server libraries are tools that facilitate the implementation of GraphQL APIs, allowing developers to define schemas, handle queries and mutations, and manage data fetching efficiently. These libraries provide various features that cater to different use cases, from simple setups to more complex, production-ready applications. They help streamline the development process by providing built-in functionalities like middleware support, subscriptions, and easy integration with existing frameworks, ultimately improving developer productivity and API performance.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
graphql-http592,103
350406 kB88 months agoMIT
graphql-yoga572,880
8,431288 kB1527 days agoMIT
express-graphql278,193
6,294-555 years agoMIT
apollo-server206,349
13,91426.6 kB712 years agoMIT
Feature Comparison: graphql-http vs graphql-yoga vs express-graphql vs apollo-server

Ease of Use

  • graphql-http:

    GraphQL HTTP is extremely lightweight and easy to set up. It provides a simple way to create a GraphQL endpoint with minimal configuration, making it ideal for quick implementations or learning purposes.

  • graphql-yoga:

    GraphQL Yoga offers a developer-friendly experience with sensible defaults and built-in features. It simplifies the setup process and provides a great starting point for new projects, making it accessible for beginners.

  • express-graphql:

    Express-GraphQL is simple to integrate into existing Express applications. It requires minimal configuration and allows developers to quickly expose a GraphQL endpoint, making it an excellent choice for those already familiar with Express.

  • apollo-server:

    Apollo Server is designed to be user-friendly, with a straightforward setup process. It provides extensive documentation and a rich set of features that make it easy to implement complex GraphQL schemas and resolvers without much boilerplate code.

Integration

  • graphql-http:

    GraphQL HTTP is designed to be framework-agnostic, allowing it to be used with any HTTP server. This flexibility makes it easy to integrate into various environments, although it may require additional setup for more complex use cases.

  • graphql-yoga:

    GraphQL Yoga is built on top of Express and integrates well with various tools and libraries in the Node.js ecosystem. It provides built-in support for features like subscriptions and file uploads, making it a versatile choice.

  • express-graphql:

    Express-GraphQL is designed to work within the Express ecosystem, allowing developers to leverage existing middleware and routing capabilities. This makes it easy to integrate GraphQL into applications that already use Express.

  • apollo-server:

    Apollo Server integrates seamlessly with various data sources, including REST APIs, databases, and other GraphQL services. It also works well with Apollo Client, allowing for a cohesive development experience across the stack.

Performance

  • graphql-http:

    GraphQL HTTP is minimalistic and performs efficiently for simple use cases. However, for more complex applications, developers may need to implement their own performance optimizations.

  • graphql-yoga:

    GraphQL Yoga is designed for performance with features like automatic persisted queries and subscriptions. It aims to provide a balance between ease of use and performance, making it suitable for a wide range of applications.

  • express-graphql:

    Express-GraphQL is lightweight and performs well for most use cases. However, it may require additional optimization for high-traffic applications, as it does not include built-in performance enhancements like caching.

  • apollo-server:

    Apollo Server is optimized for performance, offering features like query batching and caching out of the box. It also provides tools for monitoring and optimizing performance in production environments, ensuring efficient data fetching.

Community and Ecosystem

  • graphql-http:

    GraphQL HTTP is relatively new and has a smaller community. While it is straightforward to use, it may lack the extensive resources and support found in more established libraries.

  • graphql-yoga:

    GraphQL Yoga has a growing community and is part of the larger GraphQL ecosystem. It provides access to various tools and integrations, making it a solid choice for developers looking for a modern solution.

  • express-graphql:

    Express-GraphQL benefits from the large Express community, providing access to a wealth of middleware and resources. However, its ecosystem is more limited compared to Apollo Server.

  • apollo-server:

    Apollo Server has a large and active community, with extensive resources, plugins, and integrations available. This ecosystem provides support for various tools and libraries, making it a popular choice for developers.

Flexibility

  • graphql-http:

    GraphQL HTTP is highly flexible due to its minimalistic design, allowing developers to build their own custom solutions around it. However, this may require more effort for complex implementations.

  • graphql-yoga:

    GraphQL Yoga strikes a balance between flexibility and ease of use, providing sensible defaults while allowing customization for more advanced features. It is suitable for both quick prototypes and production-ready applications.

  • express-graphql:

    Express-GraphQL provides flexibility in how you structure your GraphQL server within an Express application. However, it may require more manual configuration for advanced use cases compared to other libraries.

  • apollo-server:

    Apollo Server offers a high degree of flexibility, allowing developers to customize their GraphQL implementation extensively. It supports various data sources and integrations, making it adaptable to different project requirements.

How to Choose: graphql-http vs graphql-yoga vs express-graphql vs apollo-server
  • graphql-http:

    Choose GraphQL HTTP if you need a minimalistic and straightforward solution for serving GraphQL over HTTP. It is suitable for simple use cases or when you want to quickly set up a GraphQL endpoint without additional features or dependencies.

  • graphql-yoga:

    Choose GraphQL Yoga if you want an easy-to-use and flexible GraphQL server that comes with sensible defaults and built-in features like subscriptions and file uploads. It is great for rapid prototyping and development, especially for those new to GraphQL.

  • express-graphql:

    Choose Express-GraphQL if you are already using Express.js and want a lightweight solution to add GraphQL capabilities to your existing application. It is ideal for developers looking for simplicity and direct integration with Express middleware.

  • apollo-server:

    Choose Apollo Server if you need a comprehensive and feature-rich solution that supports advanced features like caching, subscriptions, and integration with Apollo Client. It is well-suited for building production-ready GraphQL APIs with a focus on performance and developer experience.

README for graphql-http

graphql-http

Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.

Continuous integration graphql-http

Quickly check for compliance? Visit graphql-http.com!

Want a full-featured server? See the servers section!

Need subscriptions? Try graphql-ws or graphql-sse instead!


Getting started

Install

yarn add graphql-http

Create a GraphQL schema

import { GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql';

/**
 * Construct a GraphQL schema and define the necessary resolvers.
 *
 * type Query {
 *   hello: String
 * }
 */
const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      hello: {
        type: GraphQLString,
        resolve: () => 'world',
      },
    },
  }),
});

Start the server

With http
import http from 'http';
import { createHandler } from 'graphql-http/lib/use/http';
import { schema } from './previous-step';

// Create the GraphQL over HTTP Node request handler
const handler = createHandler({ schema });

// Create a HTTP server using the listener on `/graphql`
const server = http.createServer((req, res) => {
  if (req.url.startsWith('/graphql')) {
    handler(req, res);
  } else {
    res.writeHead(404).end();
  }
});

server.listen(4000);
console.log('Listening to port 4000');
With http2

Browsers might complain about self-signed SSL/TLS certificates. Help can be found on StackOverflow.

$ openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
  -keyout localhost-privkey.pem -out localhost-cert.pem
import fs from 'fs';
import http2 from 'http2';
import { createHandler } from 'graphql-http/lib/use/http2';
import { schema } from './previous-step';

// Create the GraphQL over HTTP Node request handler
const handler = createHandler({ schema });

// Create a HTTP/2 server using the handler on `/graphql`
const server = http2.createSecureServer(
  {
    key: fs.readFileSync('localhost-privkey.pem'),
    cert: fs.readFileSync('localhost-cert.pem'),
  },
  (req, res) => {
    if (req.url.startsWith('/graphql')) {
      handler(req, res);
    } else {
      res.writeHead(404).end();
    }
  },
);

server.listen(4000);
console.log('Listening to port 4000');
With express
import express from 'express'; // yarn add express
import { createHandler } from 'graphql-http/lib/use/express';
import { schema } from './previous-step';

// Create an express instance serving all methods on `/graphql`
// where the GraphQL over HTTP express request handler is
const app = express();
app.all('/graphql', createHandler({ schema }));

app.listen({ port: 4000 });
console.log('Listening to port 4000');
With fastify
import Fastify from 'fastify'; // yarn add fastify
import { createHandler } from 'graphql-http/lib/use/fastify';
import { schema } from './previous-step';

// Create a fastify instance serving all methods on `/graphql`
// where the GraphQL over HTTP fastify request handler is
const fastify = Fastify();
fastify.all('/graphql', createHandler({ schema }));

fastify.listen({ port: 4000 });
console.log('Listening to port 4000');
With Koa
import Koa from 'koa'; // yarn add koa
import mount from 'koa-mount'; // yarn add koa-mount
import { createHandler } from 'graphql-http/lib/use/koa';
import { schema } from './previous-step';

const app = new Koa();
app.use(mount('/graphql', createHandler({ schema })));

app.listen({ port: 4000 });
console.log('Listening to port 4000');
With uWebSockets.js
import uWS from 'uWebSockets.js'; // yarn add uWebSockets.js@uNetworking/uWebSockets.js#<version>
import { createHandler } from 'graphql-http/lib/use/uWebSockets';
import { schema } from './previous-step';

uWS
  .App()
  .any('/graphql', createHandler({ schema }))
  .listen(4000, () => {
    console.log('Listening to port 4000');
  });
With Deno
import { serve } from 'https://deno.land/std@0.151.0/http/server.ts';
import { createHandler } from 'https://esm.sh/graphql-http/lib/use/fetch';
import { schema } from './previous-step';

// Create the GraphQL over HTTP native fetch handler
const handler = createHandler({ schema });

// Start serving on `/graphql` using the handler
await serve(
  (req: Request) => {
    const [path, _search] = req.url.split('?');
    if (path.endsWith('/graphql')) {
      return handler(req);
    } else {
      return new Response(null, { status: 404 });
    }
  },
  {
    port: 4000, // Listening to port 4000
  },
);
With Bun
import { createHandler } from 'graphql-http/lib/use/fetch'; // bun install graphql-http
import { schema } from './previous-step';

// Create the GraphQL over HTTP native fetch handler
const handler = createHandler({ schema });

// Start serving on `/graphql` using the handler
export default {
  port: 4000, // Listening to port 4000
  fetch(req) {
    const [path, _search] = req.url.split('?');
    if (path.endsWith('/graphql')) {
      return handler(req);
    } else {
      return new Response(null, { status: 404 });
    }
  },
};
With Netlify Functions
import { createHandler } from 'graphql-http/lib/use/@netlify/functions'; // yarn add @netlify/functions
import { schema } from './previous-step';

// Create the GraphQL over HTTP native fetch handler
export const handler = createHandler({ schema });

Use the client

import { createClient } from 'graphql-http';

const client = createClient({
  url: 'http://localhost:4000/graphql',
});

(async () => {
  let cancel = () => {
    /* abort the request if it is in-flight */
  };

  const result = await new Promise((resolve, reject) => {
    let result;
    cancel = client.subscribe(
      {
        query: '{ hello }',
      },
      {
        next: (data) => (result = data),
        error: reject,
        complete: () => resolve(result),
      },
    );
  });

  expect(result).toEqual({ hello: 'world' });
})();

Serve GraphiQL

Thanks to ruru, serving GraphiQL is as easy as running:

npx ruru -SP -p 4001 -e http://localhost:4000/graphql

Open http://localhost:4001 in the browser to use it.

Recipes

🔗 Client usage with Promise
import { ExecutionResult } from 'graphql';
import { createClient, RequestParams } from 'graphql-http';
import { getSession } from './my-auth';

const client = createClient({
  url: 'http://hey.there:4000/graphql',
  headers: async () => {
    const session = await getSession();
    if (session) {
      return {
        Authorization: `Bearer ${session.token}`,
      };
    }
  },
});

function execute<Data, Extensions>(
  params: RequestParams,
): [request: Promise<ExecutionResult<Data, Extensions>>, cancel: () => void] {
  let cancel!: () => void;
  const request = new Promise<ExecutionResult<Data, Extensions>>(
    (resolve, reject) => {
      let result: ExecutionResult<Data, Extensions>;
      cancel = client.subscribe<Data, Extensions>(params, {
        next: (data) => (result = data),
        error: reject,
        complete: () => resolve(result),
      });
    },
  );
  return [request, cancel];
}

(async () => {
  const [request, cancel] = execute({
    query: '{ hello }',
  });

  // just an example, not a real function
  onUserLeavePage(() => {
    cancel();
  });

  const result = await request;

  expect(result).toBe({ data: { hello: 'world' } });
})();
🔗 Client usage with Observable
import { Observable } from 'relay-runtime';
// or
import { Observable } from '@apollo/client/core';
// or
import { Observable } from 'rxjs';
// or
import Observable from 'zen-observable';
// or any other lib which implements Observables as per the ECMAScript proposal: https://github.com/tc39/proposal-observable
import { createClient } from 'graphql-http';
import { getSession } from './my-auth';

const client = createClient({
  url: 'http://graphql.loves:4000/observables',
  headers: async () => {
    const session = await getSession();
    if (session) {
      return {
        Authorization: `Bearer ${session.token}`,
      };
    }
  },
});

const observable = new Observable((observer) =>
  client.subscribe({ query: '{ hello }' }, observer),
);

const subscription = observable.subscribe({
  next: (result) => {
    expect(result).toBe({ data: { hello: 'world' } });
  },
});

// unsubscribe will cancel the request if it is pending
subscription.unsubscribe();
🔗 Client usage with Relay
import { GraphQLError } from 'graphql';
import {
  Network,
  Observable,
  RequestParameters,
  Variables,
} from 'relay-runtime';
import { createClient } from 'graphql-http';
import { getSession } from './my-auth';

const client = createClient({
  url: 'http://i.love:4000/graphql',
  headers: async () => {
    const session = await getSession();
    if (session) {
      return {
        Authorization: `Bearer ${session.token}`,
      };
    }
  },
});

function fetch(operation: RequestParameters, variables: Variables) {
  return Observable.create((sink) => {
    if (!operation.text) {
      return sink.error(new Error('Operation text cannot be empty'));
    }
    return client.subscribe(
      {
        operationName: operation.name,
        query: operation.text,
        variables,
      },
      sink,
    );
  });
}

export const network = Network.create(fetch);
🔗 Client usage with Apollo
import {
  ApolloLink,
  Operation,
  FetchResult,
  Observable,
} from '@apollo/client/core';
import { print, GraphQLError } from 'graphql';
import { createClient, ClientOptions, Client } from 'graphql-http';
import { getSession } from './my-auth';

class HTTPLink extends ApolloLink {
  private client: Client;

  constructor(options: ClientOptions) {
    super();
    this.client = createClient(options);
  }

  public request(operation: Operation): Observable<FetchResult> {
    return new Observable((sink) => {
      return this.client.subscribe<FetchResult>(
        { ...operation, query: print(operation.query) },
        {
          next: sink.next.bind(sink),
          complete: sink.complete.bind(sink),
          error: sink.error.bind(sink),
        },
      );
    });
  }
}

const link = new HTTPLink({
  url: 'http://where.is:4000/graphql',
  headers: async () => {
    const session = await getSession();
    if (session) {
      return {
        Authorization: `Bearer ${session.token}`,
      };
    }
  },
});
🔗 Client usage with request retries
import { createClient, NetworkError } from 'graphql-http';

const client = createClient({
  url: 'http://unstable.service:4000/graphql',
  shouldRetry: async (err: NetworkError, retries: number) => {
    if (retries > 3) {
      // max 3 retries and then report service down
      return false;
    }

    // try again when service unavailable, could be temporary
    if (err.response?.status === 503) {
      // wait one second (you can alternatively time the promise resolution to your preference)
      await new Promise((resolve) => setTimeout(resolve, 1000));
      return true;
    }

    // otherwise report error immediately
    return false;
  },
});
🔗 Client usage in browser
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>GraphQL over HTTP</title>
    <script
      type="text/javascript"
      src="https://unpkg.com/graphql-http/umd/graphql-http.min.js"
    ></script>
  </head>
  <body>
    <script type="text/javascript">
      const client = graphqlHttp.createClient({
        url: 'http://umdfor.the:4000/win/graphql',
      });

      // consider other recipes for usage inspiration
    </script>
  </body>
</html>
🔗 Client usage in Node
const fetch = require('node-fetch'); // yarn add node-fetch
const { AbortController } = require('node-abort-controller'); // (node < v15) yarn add node-abort-controller
const { createClient } = require('graphql-http');

const client = createClient({
  url: 'http://no.browser:4000/graphql',
  fetchFn: fetch,
  abortControllerImpl: AbortController, // node < v15
});

// consider other recipes for usage inspiration
🔗 Client usage in Deno
import { createClient } from 'https://esm.sh/graphql-http';

const client = createClient({
  url: 'http://deno.earth:4000/graphql',
});

// consider other recipes for usage inspiration
🔗 Client usage in Bun
import { createClient } from 'graphql-http'; // bun install graphql-http

const client = createClient({
  url: 'http://bun.bread:4000/graphql',
});

// consider other recipes for usage inspiration
🔗 Server handler migration from express-graphql
import express from 'express';
import { schema } from './my-graphql-schema';

-import { graphqlHTTP } from 'express-graphql';
+import { createHandler } from 'graphql-http/lib/use/express';

const app = express();

app.use(
  '/graphql',
-  graphqlHTTP({ schema }),
+  createHandler({ schema }),
);

app.listen(4000);
🔗 Server handler usage with authentication

Authenticate the user within graphql-http during GraphQL execution context assembly. This is a approach is less safe compared to early authentication (see early authentication in Node) because some GraphQL preparations or operations are executed even if the user is not unauthorized.

import { createHandler } from 'graphql-http';
import {
  schema,
  getUserFromCookies,
  getUserFromAuthorizationHeader,
} from './my-graphql';

const handler = createHandler({
  schema,
  context: async (req) => {
    // process token, authenticate user and attach it to your graphql context
    const userId = await getUserFromCookies(req.headers.cookie);
    // or
    const userId = await getUserFromAuthorizationHeader(
      req.headers.authorization,
    );

    // respond with 401 if the user was not authenticated
    if (!userId) {
      return [null, { status: 401, statusText: 'Unauthorized' }];
    }

    // otherwise attach the user to the graphql context
    return { userId };
  },
});
🔗 Server handler usage with custom context value
import { createHandler } from 'graphql-http';
import { schema, getDynamicContext } from './my-graphql';

const handler = createHandler({
  schema,
  context: async (req, args) => {
    return getDynamicContext(req, args);
  },
  // or static context by supplying the value directly
});
🔗 Server handler usage with custom execution arguments
import { parse } from 'graphql';
import { createHandler } from 'graphql-http';
import { getSchemaForRequest, myValidationRules } from './my-graphql';

const handler = createHandler({
  onSubscribe: async (req, params) => {
    const schema = await getSchemaForRequest(req);

    const args = {
      schema,
      operationName: params.operationName,
      document: parse(params.query),
      variableValues: params.variables,
    };

    return args;
  },
});
🔗 Server handler usage in Node with early authentication (recommended)

Authenticate the user early, before reaching graphql-http. This is the recommended approach because no GraphQL preparations or operations are executed if the user is not authorized.

import { createHandler } from 'graphql-http';
import {
  schema,
  getUserFromCookies,
  getUserFromAuthorizationHeader,
} from './my-graphql';

const handler = createHandler({
  schema,
  context: async (req) => {
    // user is authenticated early (see below), simply attach it to the graphql context
    return { userId: req.raw.userId };
  },
});

const server = http.createServer(async (req, res) => {
  if (!req.url.startsWith('/graphql')) {
    return res.writeHead(404).end();
  }

  try {
    // process token, authenticate user and attach it to the request
    req.userId = await getUserFromCookies(req.headers.cookie);
    // or
    req.userId = await getUserFromAuthorizationHeader(
      req.headers.authorization,
    );

    // respond with 401 if the user was not authenticated
    if (!req.userId) {
      return res.writeHead(401, 'Unauthorized').end();
    }

    const [body, init] = await handler({
      url: req.url,
      method: req.method,
      headers: req.headers,
      body: () =>
        new Promise((resolve) => {
          let body = '';
          req.on('data', (chunk) => (body += chunk));
          req.on('end', () => resolve(body));
        }),
      raw: req,
    });
    res.writeHead(init.status, init.statusText, init.headers).end(body);
  } catch (err) {
    res.writeHead(500).end(err.message);
  }
});

server.listen(4000);
console.log('Listening to port 4000');
🔗 Server handler usage with graphql-upload and http
import http from 'http';
import { createHandler } from 'graphql-http/lib/use/http';
import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload
import { schema } from './my-graphql';

const handler = createHandler({
  schema,
  async parseRequestParams(req) {
    const params = await processRequest(req.raw, req.context.res);
    if (Array.isArray(params)) {
      throw new Error('Batching is not supported');
    }
    return {
      ...params,
      // variables must be an object as per the GraphQL over HTTP spec
      variables: Object(params.variables),
    };
  },
});

const server = http.createServer((req, res) => {
  if (req.url.startsWith('/graphql')) {
    handler(req, res);
  } else {
    res.writeHead(404).end();
  }
});

server.listen(4000);
console.log('Listening to port 4000');
🔗 Server handler usage with graphql-upload and express
import express from 'express'; // yarn add express
import { createHandler } from 'graphql-http/lib/use/express';
import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload
import { schema } from './my-graphql';

const app = express();
app.all(
  '/graphql',
  createHandler({
    schema,
    async parseRequestParams(req) {
      const params = await processRequest(req.raw, req.context.res);
      if (Array.isArray(params)) {
        throw new Error('Batching is not supported');
      }
      return {
        ...params,
        // variables must be an object as per the GraphQL over HTTP spec
        variables: Object(params.variables),
      };
    },
  }),
);

app.listen({ port: 4000 });
console.log('Listening to port 4000');
🔗 Audit for servers usage in Jest environment
import { fetch } from '@whatwg-node/fetch';
import { serverAudits } from 'graphql-http';

for (const audit of serverAudits({
  url: 'http://localhost:4000/graphql',
  fetchFn: fetch,
})) {
  test(audit.name, async () => {
    const result = await audit.fn();
    if (result.status === 'error') {
      throw result.reason;
    }
    if (result.status === 'warn') {
      console.warn(result.reason); // or throw if you want full compliance (warnings are not requirements)
    }
    // result.status === 'ok'
  });
}
🔗 Audit for servers usage in Deno environment
import { serverAudits } from 'npm:graphql-http';

for (const audit of serverAudits({
  url: 'http://localhost:4000/graphql',
  fetchFn: fetch,
})) {
  Deno.test(audit.name, async () => {
    const result = await audit.fn();
    if (result.status === 'error') {
      throw result.reason;
    }
    if (result.status === 'warn') {
      console.warn(result.reason); // or throw if you want full compliance (warnings are not requirements)
    }
    // Avoid leaking resources
    if ('body' in result && result.body instanceof ReadableStream) {
      await result.body.cancel();
    }
  });
}

Put the above contents in a file and run it with deno test --allow-net.

Only GraphQL over HTTP

This is the official GraphQL over HTTP spec reference implementation and as such follows the specification strictly without any additional features (like playgrounds or GUIs, file uploads, @stream/@defer directives and subscriptions).

Having said this, graphql-http is mostly aimed for library authors and simple server setups, where the requirements are exact to what the aforementioned spec offers.

Servers

If you want a feature-full server with bleeding edge technologies, you're recommended to use one of the following servers.

Their compliance with the GraphQL over HTTP spec is checked automatically and updated regularly.

| Name | Audit | |------|-------| | apollo-server | ✅ Compliant | | deno | ✅ Compliant | | graph-client | ✅ Compliant | | graphql-helix | ✅ Compliant | | graphql-yoga | ✅ Compliant | | hotchocolate | ✅ Compliant | | lighthouse | ✅ Compliant | | pioneer | ✅ Compliant | | postgraphile | ✅ Compliant |

Documentation

Check the docs folder out for TypeDoc generated documentation.

Audits

Inspect audits of other implementations in the implementations folder. Adding your implementation is very welcome, see how!

Want to help?

File a bug, contribute with code, or improve documentation? Read more in CONTRIBUTING.md.

If your company benefits from GraphQL and you would like to provide essential financial support for the systems and people that power our community, please also consider membership in the GraphQL Foundation.