graphql
GraphQL.js is the JavaScript reference implementation of GraphQL, the query language for APIs that Facebook created. It is the low-level engine, not a server: it gives you tools to build a type schema (programmatically or from SDL), then parse, validate, and execute queries against that schema, with resolvers that can return values, promises, or arrays of promises. Almost every GraphQL tool in the JavaScript ecosystem, from Apollo to Yoga to codegen and linters, is built on top of this package, which is why it shows up in node_modules even when you never installed it directly. Version 17 is the first new major since 2021 and requires Node 22+.
If you are doing GraphQL in JavaScript you will use this package whether you install it directly or not; it is the reference implementation and it earns that role. Just be honest about whether you need GraphQL at all: for single-client TypeScript apps, tRPC or plain REST is usually less total machinery.
Use it if
- You are building a GraphQL server, gateway, or tooling in JavaScript or TypeScript and want the reference implementation the spec evolves against
- You need to parse, validate, print, or transform GraphQL documents programmatically (linters, code generators, schema diffing, mock servers)
- You want to execute queries against a schema over your own transport instead of adopting a full server framework
- You maintain a library that accepts GraphQL schemas or documents; depending on the reference implementation is the interoperable choice
- You control both client and server and both are TypeScript; tRPC gives you end-to-end types with no schema language, no resolvers, and no codegen step
- Your API is a handful of CRUD endpoints for one client; REST plus OpenAPI is far less machinery than schema plus resolvers plus client tooling
- You expect batteries: graphql-js has no HTTP server, no subscriptions transport, no file uploads, no caching; you will be assembling graphql-yoga or @apollo/server on top anyway, so evaluate those first
- You are pinned to Node below 22; v17 requires Node 22+, and staying on v16 means living on a line whose previous minor gaps stretched years
- Your dependency tree already carries a different graphql major; duplicate copies in one node_modules produce the notorious 'from another module or realm' runtime errors that only dedupe or resolutions fix
Setup reality
npm install graphql is trivially easy: zero dependencies, about 58 KB gzipped, works in Node and browsers. The pain is ecosystem coordination, not installation. Nearly every GraphQL tool declares graphql as a peer dependency with its own accepted version range, so upgrading majors (16 to 17) means waiting until every tool in your stack accepts 17, and a stray duplicate copy in node_modules will throw realm errors at runtime. v17 also raises the floor to Node 22. Budget time for the ecosystem to catch up before you migrate an existing stack, and use your package manager's dedupe/overrides when the tree gets messy.
Patterns
Build a schema from SDL and run a querysdl-schema
import { graphql, buildSchema } from 'graphql'
const schema = buildSchema(`
type Query {
hello: String
}
`)
const rootValue = { hello: () => 'world' }
const result = await graphql({ schema, source: '{ hello }', rootValue })
console.log(result) // { data: { hello: 'world' } }buildSchema gives you a schema without resolvers wired to types; field logic lives on rootValue, which only works one level deep.
Define a schema programmatically with resolversprogrammatic-schema
import {
GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLInt
} from 'graphql'
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString }
}
})
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: UserType,
args: { id: { type: GraphQLInt } },
resolve: (_src, args) => ({ id: args.id, name: 'ada' })
}
}
})
})Programmatic schemas attach resolve functions per field, which is what buildSchema cannot do; most servers use this or a schema-building library.
Execute a query with variablesquery-variables
import { graphql } from 'graphql'
const source = `
query GetUser($id: Int!) {
user(id: $id) { name }
}
`
const result = await graphql({
schema,
source,
variableValues: { id: 42 }
})Variable names in variableValues omit the leading $; type mismatches surface as entries in result.errors, not thrown exceptions.
Handle execution errors properlyerror-handling
const result = await graphql({ schema, source })
if (result.errors) {
for (const err of result.errors) {
console.error(err.message, err.path, err.locations)
}
}
// result.data can be partially non-null even when errors existGraphQL returns partial data alongside errors; treating any errors array as total failure throws away successfully resolved fields.
Validate a query without executing itvalidate-document
import { parse, validate } from 'graphql'
const document = parse('{ user(id: 1) { nam } }')
const errors = validate(schema, document)
if (errors.length > 0) {
console.error(errors.map((e) => e.message))
// e.g. Cannot query field "nam" on type "User".
}parse throws GraphQLError on syntax problems, so wrap it in try/catch; validate returns an array and never throws.
Parse and pretty-print a documentparse-print-ast
import { parse, print } from 'graphql'
const ast = parse('query { user(id:1){ name } }')
console.log(print(ast))
// query {
// user(id: 1) {
// name
// }
// }print normalizes formatting, which makes it useful for diffing and persisting operations in a canonical form.
Use execute() when you parse and validate yourselfexecute-directly
import { parse, validate, execute } from 'graphql'
const document = parse(source)
const validationErrors = validate(schema, document)
if (validationErrors.length > 0) throw new Error('invalid query')
const result = await execute({
schema,
document,
variableValues: { id: 42 },
contextValue: { db }
})Servers cache parsed and validated documents and call execute directly; the graphql() helper re-validates on every call.
Define a custom scalar typecustom-scalar
import { GraphQLScalarType, Kind } from 'graphql'
const DateScalar = new GraphQLScalarType({
name: 'Date',
serialize: (value) => value.toISOString(),
parseValue: (value) => new Date(value),
parseLiteral: (ast) =>
ast.kind === Kind.STRING ? new Date(ast.value) : null
})parseValue handles variables, parseLiteral handles inline values in the query text; forgetting one of the two is the classic custom-scalar bug.
Execute synchronously when all resolvers are syncsync-execution
import { graphqlSync, buildSchema } from 'graphql'
const schema = buildSchema('type Query { version: String }')
const result = graphqlSync({
schema,
source: '{ version }',
rootValue: { version: '1.0.0' }
})graphqlSync throws if any resolver returns a promise; it is handy for build-time tooling and introspection scripts.
Introspect a schema to JSONintrospection
import { graphqlSync, getIntrospectionQuery, buildClientSchema } from 'graphql'
const introspection = graphqlSync({
schema,
source: getIntrospectionQuery()
}).data
// round-trip: rebuild a client-side schema from the JSON
const clientSchema = buildClientSchema(introspection)This JSON is what GraphiQL and codegen tools consume; production servers often disable introspection, so generate it at build time.
Export a schema as SDL textprint-schema-sdl
import { printSchema } from 'graphql'
const sdl = printSchema(schema)
// write it to schema.graphql for codegen, review, and diffing
import { writeFileSync } from 'node:fs'
writeFileSync('schema.graphql', sdl)Checked-in SDL snapshots make schema changes visible in code review; regenerate on every schema edit or the file lies.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @apollo/server | npm | You want a maintained, spec-compliant GraphQL HTTP server rather than wiring the bare engine yourself |
| graphql-yoga | npm | You want a batteries-included GraphQL server with sane defaults that runs on Node and edge runtimes |
| @trpc/server | npm | Both ends are TypeScript you control and you want end-to-end types without a query language |