The Guild LogoThe Guild Monogram
Docs

Yoga

Fully-featured, simple to set up, performant and extendable GraphQL JavaScript server

Get Started

Migration from Yoga V1#

Installation#

You can start with installing @graphql-yoga/node package.

yarn add @graphql-yoga/node

Server setup#

Yoga v1 no longer uses a class for constructing the server, the createServer function is now used. Also the typeDefs and resolvers config options must now be passed to a schema property.

Yoga v1

import { GraphQLServer } from 'graphql-yoga' import { typeDefs, resolvers } from './schema' const server = new GraphQLServer({ typeDefs, resolvers }) server.start()

Yoga v2

import { createServer } from '@graphql-yoga/node' import { typeDefs, resolvers } from './schema' const server = createServer({ schema: { typeDefs, resolvers }, }) server.start()

Load type definitions from a file#

In Yoga v1 it was possible to provide a file path for the typeDefs.

Yoga v1

import * as path from 'path' import { GraphQLServer } from 'graphql-yoga' import { resolvers } from './schema' const server = new GraphQLServer({ typeDefs: path.join(__dirname, 'type-definitions.graphql'), resolvers, }) server.start()

In Yoga v2 you now need to use the fs module for Node.js.

Yoga v2

import * as path from 'path' import * as fs from 'fs' import { createServer } from '@graphql-yoga/node' import { resolvers } from './schema' const server = createServer({ schema: { typeDefs: fs.readFileSync( path.join(__dirname, 'type-definitions.graphql'), 'utf-8', ), resolvers, }, }) server.start()

For more complex loading of type-definitions please refer to graphql-tools/load-files .

Schema directives (previously directiveResolvers)#

In Yoga v1 you could pass a legacy graphql-tools directiveResolver implementation to the constructor.

Yoga v1

import { GraphQLServer } from 'graphql-yoga' import { resolvers } from './schema' import { uppercaseDirectiveResolverImplementation } from './uppercase-directive-resolver-implementation' const server = new GraphQLServer({ typeDefs: /* GraphQL */ ` type Query { hi: String } `, directiveResolvers: uppercaseDirectiveResolverImplementation, }) server.start()

Before deciding upon using schema directives, you should consider whether your custom directive could be instead implemented via a field argument (abstraction).

In Yoga v2 you have to leverage the mapSchema API from graphql-tools.

yarn add @graphql-tools/utils yarn add @graphql-tools/schema

Yoga v2

import { Plugin, createServer } from '@graphql-yoga/node' import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils' import { makeExecuteableSchema } from '@graphql-tools/schema' import { defaultFieldResolver } from 'graphql' import { typeDefs, resolvers } from './schema' let schema = makeExecuteableSchema({ typeDefs: [ typeDefs, /* GraphQL */ ` directive @uppercase on FIELD_DEFINITION `, ], resolvers, }) schema = mapSchema(schema, { [MapperKind.OBJECT_FIELD]: (fieldConfig) => { const upperDirective = getDirective(schema, fieldConfig, 'uppercase')?.[0] if (upperDirective) { const { resolve = defaultFieldResolver } = fieldConfig return { ...fieldConfig, resolve: async function (source, args, context, info) { const result = await resolve(source, args, context, info) if (typeof result === 'string') { return result.toUpperCase() } return result }, } } }, }) const server = createServer({ schema, })

You can learn more about this practise within the graphql-tools schema directives documentation.

Context#

In GraphQL Yoga v2 you can use the context property in the same way as GraphQL Yoga v1. The value returned from the context factory will be merged with the initial context.

The request property within the initial context is now a Fetch API Request. It can be used for accessing all the HTTP request parameters, such as headers or the method (POST, GET).

You can learn more about the context within the context documentation.

Yoga v1

import { GraphQLServer } from 'graphql-yoga' import { typeDefs, resolvers } from './schema' import { db } from './db' const server = new GraphQLServer({ typeDefs, resolvers, context: (initialContext) => { const authHeader = initialContext.request.headers['authorization'] ?? null return { ...initialContext, db, authHeader } }, }) server.start()

Yoga v2

import { createServer } from '@graphql-yoga/node' import { typeDefs, resolvers } from './schema' import { db } from './db' const server = new GraphQLServer({ schema: { typeDefs, resolvers, }, context: (initialContext) => { const authHeader = initialContext.request.headers.get('authorization') ?? null return { db, authHeader } }, }) server.start()

Middlewares#

GraphQL Yoga v1 included graphql-middleware for wrapping resolver functions with common logic. GraphQL Yoga v2 no longer includes graphql-middleware by default as using it can result in bad performance as it wraps all field resolvers within the schema.

If you cannot migrate your graphql-middleware code to something like graphql-tool mapSchema, we recommend using the @envelop/graphql-middleware plugin.

yarn add @envelop/graphql-middleware

Yoga v1

import { GraphQLServer } from 'graphql-yoga' import { typeDefs, resolvers } from './schema' // Middleware - Permissions const code = 'supersecret' const isLoggedIn = async (resolve, parent, args, ctx, info) => { // Include your agent code as Authorization: <token> header. const permit = ctx.request.get('Authorization') === code if (!permit) { throw new Error(`Not authorised!`) } return resolve() } const permissions = { Query: { secured: isLoggedIn, }, Me: isLoggedIn, } const server = new GraphQLServer({ typeDefs, resolvers, middleware: [permissions], }) server.start()

Yoga v2

import { createServer } from '@graphql-yoga/node' import { useGraphQLMiddleware } from '@envelop/graphql-middleware' import { typeDefs, resolvers } from './schema' // Middleware - Permissions const code = 'supersecret' const isLoggedIn = async (resolve, parent, args, ctx, info) => { // Include your agent code as Authorization: <token> header. const permit = ctx.request.get('Authorization') === code if (!permit) { throw new Error(`Not authorised!`) } return resolve() } const permissions = { Query: { secured: isLoggedIn, }, Me: isLoggedIn, } const server = createServer({ schema: { typeDefs, resolvers }, plugins: [useGraphQLMiddleware([permissions])], }) server.start()

For more details please refer to the

Replacing GraphQL Shield#

If you are using graphql-shield you might wanna have a look and see whether the following plugins might replace it:

Subscriptions#

GraphQL Yoga v1 uses the old and deprecated subscriptions-transport-ws protocol. GraphQL Yoga v2 comes with built in subscription support over SSE (Server Sent Events). One benefit of this is that you no longer need an additional library on your frontend as the SSE protocol is just simple HTTP.

Because of the protocol change you must migrate your GraphQL clients that execute GraphQL subscription operations to use the new protocol. Please use the code snippets for your GraphQL client as listed on the handle subscription on the client documentation.

Advantages of SSE over Websockets#

  • Transported over simple HTTP instead of a custom protocol
  • Built in support for re-connection and event-id Simpler protocol
  • No trouble with corporate firewalls doing packet inspection

Advantages of Websockets over SSE#

  • Real time, two directional communication.

SSE gotchas#

PubSub#

With GraphQL Yoga v1 used the unmaintained package graphql-subscriptions for the PubSub implementation. In GraphQL Yoga v2, a new maintained PubSub implementation is built-in.

Yoga v1

import { PubSub } from 'graphql-yoga' const pubSub = new PubSub()

Yoga v2

import { createPubSub } from '@graphql-yoga/node' const pubSub = createPubSub()

Type-safe PubSub Usage#

The old PubSub implementation was not type-safe. Now it is possible to define all the events and payloads. For a full reference please check out the Subscription PubSub documentation.

Yoga v1

import { GraphQLServer, PubSub } from 'graphql-yoga' const pubSub = new PubSub() const server = new GraphQLServer({ context: { pubSub }, typeDefs: /* GraphQL */ ` type Query { _: Boolean } type Subscription { randomNumber: Int! } type Mutation { publishRandomNumber(randomNumber: Int!): Boolean } `, resolvers: { Subscription: { randomNumber: { subscribe: (_, _, context) => { return context.asyncIterator('randomNumber') }, resolve: (value) => value, }, }, Mutation: { publishRandomNumber: (_, args, context) => { context.pubSub.publish('randomNumber', args.randomNumber) }, }, }, }) server.start()

Yoga v2

import { createPubSub, createServer } from '@graphql-yoga/node' const pubSub = new PubSub<{ randomNumber: [randomNumber: number] }>() const server = createServer({ context: { pubSub }, schema: { typeDefs: /* GraphQL */ ` type Query { _: Boolean } type Subscription { randomNumber: Int! } type Mutation { publishRandomNumber(randomNumber: Int!): Boolean } `, resolvers: { Subscription: { randomNumber: { subscribe: (_, _, context) => { return context.subscribe('randomNumber') }, resolve: (value) => value, }, }, Mutation: { publishRandomNumber: (_, args, context) => { context.pubSub.publish('randomNumber', args.randomNumber) }, }, }, }, }) server.start()

Filtering events#

Instead of the withFilter function you can now use the more modular pipe and filter functions exported from @graphql-yoga/node. You can learn more about filtering and mapping values in the subscription filter and map values documentation.

Yoga v1

import { GraphQLServer, PubSub, withFilter } from 'graphql-yoga' const pubSub = new PubSub() const server = new GraphQLServer({ context: { pubSub }, typeDefs: /* GraphQL */ ` type Query { _: Boolean } type Subscription { randomNumber(greaterThan: Int!): Int! } type Mutation { publishRandomNumber(randomNumber: Int!): Boolean } `, resolvers: { Subscription: { randomNumber: { subscribe: withFilter( (_, _, context) => { return context.asyncIterator('randomNumber') }, (payload, args) => payload > args, ), resolve: (value) => value, }, }, Mutation: { publishRandomNumber: (_, args, context) => { context.pubSub.publish('randomNumber', args.randomNumber) }, }, }, }) server.start()

Yoga v2

import { createPubSub, createServer, pipe, filter } from '@graphql-yoga/node' const pubSub = new PubSub<{ randomNumber: [randomNumber: number] }>() const server = createServer({ context: { pubSub }, schema: { typeDefs: /* GraphQL */ ` type Query { _: Boolean } type Subscription { randomNumber(greaterThan: Int!): Int! } type Mutation { publishRandomNumber(randomNumber: Int!): Boolean } `, resolvers: { Subscription: { randomNumber: { subscribe: (_, args, context) => { return pipe( context.subscribe('randomNumber'), filter((value) => value > args.greaterThan), ) }, resolve: (value) => value, }, }, Mutation: { publishRandomNumber: (_, args, context) => { context.pubSub.publish('randomNumber', args.randomNumber) }, }, }, }, }) server.start()