Hello Nexus Framework User!

If you are a nexus (Nexus Framework) user we strongly encourage you to migrate to @nexus/schema (Nexus Schema). For more detail about why and how this came about please see this announcement. The following guide is written to help you transition from the framework back to the library.

Note that there is a separate migration guide detailing how to go from the Prisma plugin for Nexus Framework to the Prisma plugin for Nexus schema.

Bundled Dependencies

You will need to install @nexus/schema and graphql yourself. While you're at it, uninstall nexus too.

1npm uninstall nexus
2npm install @nexus/schema graphql

JavaScript Support

If you had been using TypeScript before only because Nexus Framework forced you to, then know that you can use JavaScript with @nexus/schema if you want. The rest of this guide will assume that you want to continue using TypeScript. Ignore or adapt instructions as needed if you are a JS user.

Managed TypeScript

You will need to manage the TypeScript toolchain yourself.

  1. Install typescript, ts-node, and ts-node-dev.

    1npm add -D typescript ts-node ts-node-dev
  2. The following applies to VSCode but something like it might apply to other editors as well. You must also make sure your editor is set to use the local version of TypeScript rather than the one the editor ships with. Summon the command pallet command+shift+p and then enter and select typescript: select TypeScript Version.... Then select Workspace Version.

  3. Ensure your tsconfig settings are correct.

    1. If you are outputting Nexus Schema typegen to node_modules/@types/<something>/index.d.ts like Nexus Framework did then you must ensure that the compilerOptions.typeRoots and compilerOptions.types fields are correctly set or not set at all. If typeRoots is set then ensure that where you output Nexus Schema's typegen is listed. If types is set then ensure that the parent directory where you output Nexus Schema's typegen is listed.
    2. If you are using Apollo Server then you must enable skipLibCheck: true in compilerOptions.
    3. Enable strict mode in compilerOptions to benefit most of all from Nexus Schema's typings.

Development Mode

You will need to manage running and restarting your server on changes during development yourself. If you are using TypeScript then use the following two development scripts dev and dev:typecheck. Together, these attempt to smooth over the workflow with Nexus' reflection system and approximate a subset of what nexus dev did.

1{
2 "scripts": {
3 "dev": "ts-node-dev --transpile-only ./your/main/module",
4 "dev:typecheck": "tsc --noEmit --watch",
5 }
6}

The dev script launches a ts-node-dev process that is responsible for running your API during development. You don't want this process to perform type checking because doing so would halt programming running, and that is incompatible with Nexus' reflection system because it depends on your program running to generate your static schema types.

The following applies to VSCode but it might apply to other editors as well: You cannot fully rely on your editor for static type checking because it only checks files that are open, not your entire project. There are some ways to configure this and additional VSCode extensions you can try to achieve this. If those don't work for you then simply run another process in a new terminal window/shell session. That is what dev:typecheck script does. It launches a tsc process that is responsible for type checking your code during development. You don't want this process to perform transpilation as that is already being handled by the dev script.

Nexus Module Discovery & Singleton Schema Builder

You will need to export Nexus Schema type definition values and import them into where you run your makeSchema. The exact module layout you use does not matter.

In framework you could do this:

1import { schema } from 'nexus'
2
3schema.objectType(...)

Now you will need to do something like this:

1// ./User.ts
2
3import { objectType } from '@nexus/schema'
4
5export const User = objectType(...)
1// ./index.ts
2
3import { makeSchema } from '@nexus/schema'
4import { User } from './User'
5
6const schema = makeSchema({
7 types: [User],
8 ...
9})

Fully Abstracted Reflection System

You need to run Nexus Schema reflection yourself explicitly. You need to do this before running tsc to build your app. Otherwise tsc will fail due to type errors because of the missing typings files.

There are two primary ways you can go about this.

  1. Approach A: Run the subset of your app that contains the schema and that you know will exit.
  2. Approach B: Run your app as usual, but configure it up to exit once reflection has completed.

Approach A might be simpler if your project can accommodate it. The primary requirement to accomodate it is that this subset of the application can be run to completion. That is, you can run ts-node path/to/module and it exits. If it doesn't exit that means you have something keeping the node process open like an awaited unresolved promise or open handle (e.g. a Prisma client connection).

Approach A is simpler in that it reduces the surface area of your codebase where you need to think about eager code running during reflection (which in turn is run before build and tests). On the other hand you may already be thinking about this if you are trying to develop testable code. Use whichever approach works best for you.

Regardless of which choice you go with, you also need to ensure that NODE_ENV=production is set. If you forget to do this then Nexus Schema will (by default) run reflection. This would significantly slow down your application boot time, something you don't want in serverless environments, for example.

Here is an example of approach A to managing reflection:

  1. Layout your Nexus Schema type defs amongst modules under a parent dir such that each module contains a part of your GraphQL schema. Put shared parts of your GraphQL schema into shared.ts. Re-export all type defs in index.ts.

    1/src
    2 /graphql
    3 User.ts
    4 Comment.ts
    5 shared.ts
    6 index.ts
  2. Your index.ts should import all type defs.

    1// src/graphql/index.ts
    2
    3export * from './User'
  3. Add a schema.ts module that imports all type defs and calls makeSchema.

    1// src/schema.ts
    2
    3export { makeSchema } from '@nexus/schema'
    4import * as types from './graphql'
    5
    6export const schema = makeSchema({
    7 types,
    8})
  4. Now you can run Nexus Schema reflection by running this subset of your app e.g. ts-node --transpile-only src/schema. You will probably want to capture this via a new npm script called e.g. nexus:reflect.

Here is an example of approach B to managing reflection:

  1. Setup your app however you want

  2. When configuring makeSchema make sure to enable process exit when requested via an environment variable.

    1import { makeSchema } from '@nexus/schema'
    2
    3export const schema = makeSchema({
    4 shouldExitAfterGenerateArtifacts: Boolean(process.env.NEXUS_SHOULD_EXIT_AFTER_REFLECTION),
    5})
  3. Now you can run Nexus Schema reflection by running your app with that environment variable set e.g. NEXUS_SHOULD_EXIT_AFTER_REFLECTION=true ts-node src. You will probably want to capture this via a new script called e.g. nexus:reflect.

Integrated Build Step With Bundling

You will need to handle the build yourself. This involves a few things:

  1. Run Nexus Schema reflection yourself

    This has been explained in the Fully Abstracted Reflection System section.

  2. Run tsc yourself

    Straightforward. Note that, If you are doing Step 3 then this step may be redundant.

  3. Optional: Run a bundler yourself

    This is probably something you don't need to do. Nexus Framework took a packages-bundled approach to dependencies and this mean a large package size that bloated deployments necessitating techniques like tree-shaking (e.g. TypeScript dependency is over 50mb). This problem generally goes away when using the Nexus Schema library directly. However bundling for serverless deployments can still be desirable especially if your non-development dependencies are heavy and not fully used at runtime. If you were relying on Nexus Framework bundling here are some tools you can try out yourself:

    These tools take care of TypeScript transpilation so you should be able to skip using tsc manually with the above tools.

    When you are bundling your app you may need to tweak your tsconfig.json to output ES modules rather than CJS modules. The options you will need to think about are module, moduleResolution, and target. There are few ways to go about this, the following is one:

    1{
    2 "compilerOptions": {
    3 "moduleResolution": "Node",
    4 "target": "ES2015",
    5 "module": "ES2015"
    6 }
    7}

Here is an example where bundling is not used:

1{
2 "scripts": {
3 "build": "yarn nexus:reflection && tsc",
4 "nexus:reflection": "ts-node path/to/schema/modules/entrypoint"
5 }
6}

HTTP Server

Nexus framework comes bundled with Apollo Server and runs it for you. You will need to figure out your server solution yourself. One common option is Apollo Server. It is actually a middleware layer that integrates with many different HTTP frameworks and libraries. Here is an example using Apollo Server with Express.

  1. Install new dependencies

    1npm add express apollo-server-express
  2. If using TypeScript then Install typings for Express

    1npm add -D @types/express
  3. If using TypeScript then you need to enable esModuleInterop to be able to use Apollo Server

    1// tsconfig.json
    2{
    3 "compilerOptions": {
    4 "esModuleInterop": true
    5 }
    6}
  4. Write your server code

    1import { ApolloServer } from 'apollo-server-express'
    2import { makeSchema, objectType } from '@nexus/schema'
    3import createExpress from 'express'
    4
    5const schema = makeSchema({
    6 types: [objectType({ ... })]
    7})
    8
    9const apollo = new ApolloServer({
    10 schema
    11})
    12
    13const express = createExpress()
    14
    15apollo.applyMiddleware({ app: express })
    16
    17express.listen(4000, () => {
    18 console.log(`🚀 GraphQL service ready at http://localhost:4000/graphql`)
    19})

HTTP Server CORS Support

Nexus Framework shipped with builtin server CORS support. You can easily achieve this yourself with Apollo Server. Following from example in the HTTP Server section, you can setup your CORS settings in the applyMiddleware function:

1apollo.applyMiddleware({ app: express, cors: { ... } })

Integrated GraphQL Playground

You need to integrate GraphQL Playground yourself if you want it. An easy way to do this is use Apollo Server which ships with GraphQL Playground enabled in development by default.

Subscriptions Server

Nexus Frameworks automatically enables a subscriptions server when you use the GraphQL Subscription type. One way to setup a subscriptions server yourself is with Apollo Server. For details refer to their docs.

Following from example in the HTTP Server section, here is an example with a subscriptions server:

1import * as Http from 'http'
2import { ApolloServer } from 'apollo-server-express'
3import { makeSchema, subscriptionType } from '@nexus/schema'
4import createExpress from 'express'
5
6/**
7 * An example streaming data source.
8 */
9async function* truthStream() {
10 while (true) {
11 const answer = [true, false][Math.round(Math.random())]
12 yield answer
13 await new Promise(res => setTimeout(res, 1000))
14 }
15}
16
17/**
18 * An example subscription type.
19 */
20const Subscription = subscriptionType({
21 definition(t) {
22 t.field('foobars', {
23 type: 'Boolean',
24 subscribe() {
25 return truthStream()
26 },
27 resolve(eventData: boolean) {
28 return eventData
29 },
30 })
31 },
32})
33
34const schema = makeSchema({
35 types: [Subscription],
36})
37
38const apollo = new ApolloServer({
39 schema,
40})
41
42const express = createExpress()
43
44const httpServer = Http.createServer(app)
45
46apollo.installSubscriptionHandlers(httpServer)
47
48apollo.applyMiddleware({ app: express })
49
50httpServer.listen({ port: 4000 }, () => {
51 console.log(`server at http://localhost:4000${apollo.graphqlPath}`)
52 console.log(`Subscriptions server at ws://localhost:4000${apollo.subscriptionsPath}`)
53})

Global Error Handling

You will need to handle global error handling yourself. Nexus framework previously injected code that would catch synchronous and asynchronous unhandled errors, log them, and exit the process. For synchronous errors, this merely emulates Node's default behavior. However for asynchronous errors, it provided behavior that Node states it will eventually have one day.

The simplest thing you can do here other than doing nothing is use make-promises-safe.

Builtin Logger & Logging

You will need to handle logging yourself. If you liked the @nexus/logger from Nexus Framework you can find it now as floggy on npm. Another great logger you might consider is pino. The following assumes floggy but adapt as needed.

Nexus Framework has some builtin logging functionality. You can approximate it as follows.

  1. Install your logger

    1npm add floggy
  2. Create your application logger. The logger you export here should be used across your codebase.

    1import * as Floggy from 'floggy'
    2
    3Floggy.settings({ filter: 'app:*, *@warn+' })
    4export const log = Floggy.log.child('app')
  3. Create your request logger. The idea here is to have a logger scoped to each request. You can achieve this with different loggers than floggy (e.g. pino) and with different servers than Apollo Server.

    1import { ApolloServer } from 'apollo-server-express'
    2import { log } from './path/to/application/logger/module'
    3
    4const server = new ApolloServer({
    5 context: (request) => {
    6 const requestLogger = log.child('request')
    7 requestLogger.addToContext({ ... }) // e.g. user ID
    8 return { log: requestLogger }
    9})
  4. You still need to make this type safe. See "Context API Type Discovery" section for more detail. What follows is just a code example without explanation.

    1import * as Nexus from '@nexus/schema'
    2import * as Floggy from 'floggy'
    3
    4export type Context = {
    5 log: Floggy.Logger
    6}
    7
    8Nexus.makeSchema({
    9 typegenAutoConfig: {
    10 sources: [{ source: __filename, alias: 'ContextModule' }]
    11 contextType: 'ContextModule.Context',
    12 },
    13 ...
    14})

Bundled Scalars

You need to explicitly setup all custom scalars yourself. To match what Nexus Framework does by default you need to do the following:

  1. Install the graphql-scalars package

    1npm add graphql-scalars
  2. Setup the Json and DateTime scalars

    1import { makeSchema } from '@nexus/schema'
    2import { DateTimeResolver, JSONObjectResolver } from 'graphql-scalars'
    3
    4const jsonScalar = asNexusMethod(JSONObjectResolver, 'json')
    5const dateTimeScalar = asNexusMethod(DateTimeResolver, 'date')
    6
    7const schema = makeSchema({ types: [jsonScalar, dateTimeScalar] })

Context API Type Discovery

In Nexus Framework the context parameter of resolver functions is automatically typed.

1import { schema } from 'nexus'
2
3schema.addToContext(() => { qux: 1 })
4
5schema.objectType({
6 name: "Foo",
7 definition(t) {
8 t.string("bar", {
9 resolve(parent, args, ctx) {
10 ctx.qux // statically typed as: number
11 ...
12 }
13 })
14 }
15})

To achieve the same level of type safety you now need to do the following.

  1. Define and export a static context type
  2. Configure Nexus Schema to use it
  3. Attach your data to the context. How to do this depends on the GraphQL server abstraction you are using usually. The example below uses Apollo Server.

Example:

1// 1
2// contextModule.ts
3
4export type Context = {
5 qux: number
6}
1// 2
2
3import * as Nexus from '@nexus/schema'
4import * as Path from 'path'
5
6Nexus.makeSchema({
7 typegenAutoconfig: {
8 sources: [{
9 source: Path.join(__dirname, './path/to/contextModule'),
10 alias: 'ContextModule'
11 }],
12 contextType: 'ContextModule.Context',
13 },
14 ...
15})
1// 3
2
3import { ApolloServer } from 'apollo-server-express'
4
5const server = new ApolloServer({
6 context: () => ({ qux: 1 }),
7})

Backing Types Type Discovery

In Nexus Framework backing types worked like this:

  1. Export a type from any module in your app
  2. Its name appears as an option among the string literal union populating the rootTyping field of Nexus Schema object type definitions.

Nexus Schema does not have this functionality. There are a few things you can do instead.

  • Approach A: If you have a set of exported Backing types whose names match your GraphQL objects then you can set their modules as sources in typegenAutoconfig and Nexus Schema will take care of the rest.
  • Approach B: If you have a set of Backing types whose names having a differing pattern than your GraphQL objects then supply a Regular Expression to the typeMatch property in typegenAutoconfig. You can for example map BackingUser -> User , BackingPost -> Post and so on.
  • Approach C: If you have a one-off mismatch then supply an arbitrary mapping to the backingTypeMap property of typegenAutoconfig.
  • Approach D: If you want to colocate a type mapping like how it was in Nexus Framework then use the rootTyping field on Nexus Schema type defs. Unlike framework it accepts an object with a path to a module and name of the type to look for being exported from that module.

Here is an example using approach A:

1// 1
2// someModule.ts
3
4export type Foo = {
5 bar: string
6}
1// 2
2
3import * as Nexus from '@nexus/schema'
4import * as Path from 'path'
5
6Nexus.makeSchema({
7 typegenAutoconfig: {
8 sources: [{
9 source: Path.join(__dirname, 'path/to/someModule'),
10 alias: 'SomeAlias'
11 }],
12 },
13 types: [
14 Nexus.queryType({
15 definition(t) {
16 t.list.field('foos', {
17 type: 'Foo',
18 resolve(parent, args, ctx) {
19 return ctx.getFoos() // type-safe to backing types
20 }
21 })
22 }
23 }),
24 Nexus.objectType({
25 name: 'Foo', // "Foo" backing type matches to this GraphQL object
26 definition(t) {
27 t.string('barcola', {
28 resolve(parent) => {
29 return parent.bar // type safe thanks to backing types
30 }
31 })
32 }
33 })
34 ],
35})

Integrated System Testing

In Nexus Framework there was a testing module for system testing your app. You now need to figure this out yourself. There are several frameworks you can choose from such as jest and ava. This guide assumes jest.

  1. Install your test framework

    1npm add jest
  2. If you are a TypeScript user then install and configure ts-jest

    1npm add ts-jest

    We will configure using a jest.config.js module in your project root. Do not use a JSON based configuration unless you know that you won't need the dynamic code used in some later steps here.

    1// jest.config.js
    2
    3module.exports = {
    4 preset: 'ts-jest',
    5 testEnvironment: 'node',
    6}
  3. Setup your project to be able to soundly run one-off tests In order to run your test suite you typically need your app to be type checking. Given the Nexus Schema reflection system, this means that we need to run the app before running the tests. To setup your project for running reflection see the section Fully Abstracted Reflection System.

    Here is an example of how this might look in your project:

    1{
    2 "scripts": {
    3 "test": "yarn nexus:reflection && jest",
    4 "nexus:reflection": "ts-node path/to/schema/modules/entrypoint"
    5 }
    6}
  4. Understand your TDD workflow option e.g. jest --watch In this case you need Nexus Schema reflection to be running after every change to the source code. But this also typically triggers a new test run. That test run could be faster than reflection, leading to temporary type errors that don't matter to you. You don't want these type errors to block your TDD flow otherwise you get a 🐔🥚problem. To solve this you configure ts-jest to warn instead failing on type errors. The workflow then goes like this: 1. You start your test framework in watch mode 2. You edit your source code 3. Your source code under test runs. This in turn is running Nexus schema reflection. 4. Type errors from your initial test results should be ignored. Run the test suite again (e.g. in jest watch mode hit enter). Now it is running with the complete set of types and you can trust the test results.

    There are some caveats here:

    1. Your tests need be importing the whole of your nexus schema modules and makeSchema code. Otherwise reflection will result in partially generated types that may other kinds of type errors you don't care about (for example a GraphQL string-literal-based object type reference becomes invalid).
    2. And yes, every test run following change(s) potentially requiring two runs is not ideal

    Here is the revised jest configuration you'll need.

    1// jest.config.js
    2
    3module.exports = {
    4 preset: 'ts-jest',
    5 testEnvironment: 'node',
    6 globals: {
    7 'ts-jest': {
    8 diagnostics: {
    9 warnOnly: true,
    10 },
    11 },
    12 },
    13}

    Here is how your script for starting your tdd flow in your project might look:

    1{
    2 "scripts": {
    3 "tdd": "jest --watch"
    4 }
    5}
  5. Setup your project to be able to soundly run on CI

    We've added the warnOnly setting as a way to support TDD but now we can't be as sure things are good after one-off test that we expect to include type checking since invalid types will not halt the test from passing. This is mostly a problem in CI where we're aren't interacting with the test run.

    Here is one example of how you might solve this issue:

    1// jest.config.js
    2
    3module.exports = {
    4 preset: 'ts-jest',
    5 testEnvironment: 'node',
    6 globals: {
    7 'ts-jest': {
    8 diagnostics: {
    9 warnOnly: !Boolean(process.env.TYPECHECK),
    10 },
    11 },
    12 },
    13}
    1{
    2 "scripts": {
    3 "test": "yarn nexus:reflection && TYPECHECK=true jest"
    4 }
    5}
  6. You will need a way to run your app in tests. Here is one way adapted from the tutorial.

    1npm add -D graphql-request
    1import { GraphQLClient, gql } from 'graphql-request'
    2import app from '...' // Adapt to your project
    3
    4type TestContext = {
    5 client: GraphQLClient
    6}
    7
    8export function createTestContext(): TestContext {
    9 let ctx = {} as TestContext
    10
    11 beforeEach(async () => {
    12 const port = await getPort({ port: makeRange(4000, 6000) })
    13 await app.start({ port }) // Adapt to your project
    14 Object.assign(ctx, {
    15 client: new GraphQLClient(`http://localhost:${port}`),
    16 })
    17 })
    18
    19 afterEach(async () => {
    20 await app.stop({ port })
    21 })
    22
    23 return ctx
    24}
    25
    26const ctx = createTestContext()
    27
    28it('works', async () => {
    29 expect(
    30 await ctx.client.query(gql`
    31 ...
    32 `)
    33 ).toMatchSnapshot()
    34})

Project Scaffolding

Nexus Framework had a CLI for scaffolding new projects. You can approximate this by cloning the examples repo and copying the source code of one of the projects.

Settings API

Nexus Framework has a gradual settings API with features such as automatic mapping of environment variables to settings. You can use the setset package manually to gain back this functionality.

1npm add setset
1import * as Setset from 'setset'
2
3type Input = {
4 server?: {
5 port?: number
6 }
7}
8
9const settings = Ssetset.create<Input>({
10 fields: {
11 port: {
12 initial: () => 4000,
13 },
14 },
15})
16
17settings.data.server.port // 4000

Other Differences

Some differences between Nexus Framework and Nexus Schema and cannot be easily approximated. The follow gathers these differences for your convenience.

  1. CLI
  2. Framework Plugins
  3. Zero config experience
  4. Pretty error messages during development
  5. TypeScript Language Service Plugin for refined autocomplete experience
  6. Centralized settings
Edit this page on Github