GraphQL

A GraphQL Server Beside Your Express Routes

A GraphQL server is middleware: hand it a schema, mount it at a path, and your routes, helmet 10,736 configuration and error handler stay as they were. Three maintained libraries do this on Express 24,430 , all MIT: @apollo/server 5.5.1 (14k stars, the largest ecosystem), graphql-http 1.23.0 (the reference implementation), and graphql-yoga 5.24.1 8,529 (8.5k stars). The choice turns on which graphql version each allows: Apollo Server 54,654 declares graphql: ^16.11.0, so pairing it with the current 17.0.2 fails with npm 2,036 error ERESOLVE ... peer graphql@"^16.11.0". graphql-yoga (https://github.com/graphql-hive/graphql-yoga 8,529 ) accepts graphql 15 through 17 and is what the listings use: npm install graphql@17.0.2 graphql-yoga@5.24.1.

Mounting a GraphQL endpoint next to REST routesJavaScript
import express from 'express';
import { createSchema, createYoga } from 'graphql-yoga';
// books and authors are the fixture arrays of Section 3.9: { id, title, year, authorId }
const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Author { id: ID!, name: String! }
    type Book { id: ID!, title: String!, year: Int!, author: Author! }
    type Query { books(since: Int): [Book!]! }
    type Mutation { addBook(title: String!, year: Int!, authorId: ID!): Book! }
  `,
  resolvers: {
    Query: { books: (_p, { since }) => books.filter((b) => !since || b.year >= since) },
    Mutation: {                                 // arguments arrive as one object
      addBook: (_p, input) => books[books.push({ id: String(books.length + 1), ...input }) - 1]
    },
    Book: { author: (book) => authors.find((a) => a.id === book.authorId) }
  }
});
const app = express();
app.get('/health', (_req, res) => res.json({ ok: true }));   // your REST routes, untouched
app.use('/graphql', createYoga({ schema }));
app.listen(4010);

Yoga reads the raw stream, or req.body when a parser got there first, so mounting it before or after express.json() works; Apollo's integration insists on express.json() first. Three requests sent as application/json: a query across two types, a mutation with variables, and a misspelled field.

Output of 75
200 {"data":{"books":[{"title":"Neuromancer","author":{"name":"William Gibson"}}]}}
200 {"data":{"addBook":{"id":"4","title":"Agency"}}}
200 {"errors":[{"message":"Cannot query field \"titel\" on type \"Book\".
Did you mean \"title\"?","locations":[{"line":1,"column":11}],
"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}]}

The since: 1980 argument filtered the list, author resolved a second type in the same response, and the mutation returned the created node rather than a Location header. Note the third status code: 200 for a query that never executed. That is content negotiation — Accept: application/graphql-response+json gets 400 for the same request, while an application/json client gets 200, so older clients never read a validation error as a transport failure. A resolver that throws gives 200 with {"data":{"boom":null}} and "Unexpected error." in place of the message.