February 07, 2019

Introducing GraphQL Nexus: Code-First GraphQL Server Development

In our last post, we outlined the issues with SDL-first GraphQL server development. This week, we're excited to announce GraphQL Nexus, a code-first GraphQL library. A guest post by Tim Griesser.

This article is the second in a three-part series:

Follow us on Twitter to get notified when the upcoming articles are published.


Recap: The issues with SDL-first development

As outlined in the previous post, SDL-first GraphQL server development has a number of challenges, such as keeping SDL and resolvers in sync, modularizing your GraphQL schema, and achieving great IDE support. Most of the problems can be solved, but only at the cost of learning, using and integrating a myriad of additional tools.

Today we are introducing a library that implements the code-first approach for GraphQL server development: GraphQL Nexus.


Introducing GraphQL Nexus

The best of both worlds: Schema-first & code-first

In the last article, we developed an understanding for schema-first, SDL-first, and code-first approaches for building GraphQL servers:

  • Schema-first: Upfront schema design is a crucial part of the development process
  • SDL-first: SDL-version of the GraphQL schema is the source of truth for the API
  • Code-first: The GraphQL schema is constructed programmatically

While being a code-first framework, GraphQL Nexus can still be used for schema-first development. Schema-first and code-first are not opposing approaches: they become even more useful when combined.

With Nexus, the GraphQL schema is defined and implemented programmatically. It therefore follows proven approaches of GraphQL servers in other languages, such as sangria-graphql (Scala), graphlq-ruby or graphene (Python).

Type-safe, compatible with GraphQL ecosystem & data-agnostic

GraphQL Nexus was designed with TypeScript/JavaScript intellisense in mind. It combines TypeScript generics, conditional types, and type merging to provide full auto-generated type coverage. A core design goal of Nexus is to have the best possible type coverage with the least possible manual type annotation.

Nexus builds upon the primitives of graphql-js which makes it largely compatible with the current GraphQL ecosystem.

Defining and implementing a GraphQL schema with Nexus

The API of Nexus exposes a number of functions that let you define and implement the building blocks for your GraphQL schema, such as object types, unions & interfaces, enums and everything else you find in GraphQL's type system:

Object Types
Unions
Interfaces
Input Types
Enums
Scalars
const User = objectType({
  name: 'User',
  definition(t) {
    t.int('id', { description: 'Id of the user' })
    t.string('fullName', { description: 'Full name of the user' })
    t.list.field('posts', {
      type: Post, // or "Post"
      resolve(root, args, ctx) {
        return ctx.getUser(root.id).posts()
      },
    })
  },
})

const Post = objectType({
  name: 'Post',
  definition(t) {
    t.int('id')
    t.string('title')
  },
})

The Query and Mutation types are the so-called root types in a GraphQL schema. Nexus provides a shorthand API to define those:

Query
Mutation
const Query = queryType({
  definition(t) {
    t.field('user', {
      type: User,
      nullable: true,
      args: { id: idArg({ nullable: false }) },
      resolve: (parent, { id }) => fetchUserById(id),
    })
  },
})

Once you have defined all of the types for your GraphQL schema, you can use the makeSchema function to create a GraphQLSchema instance that will be the foundation for your GraphQL server (e.g. graphql-yoga or apollo-server):

const schema = makeSchema({
  // The programmatically defined building blocks of your GraphQL schema
  types: [User, Query, Mutation],

  // Specify where the generated TS typings and SDL should be located
  outputs: {
    typegen: __dirname + '/generated/typings.ts',
    schema: __dirname + '/generated/schema.graphql',
  },

  // All input arguments and return types are non-null by default
  nonNullDefaults: {
    input: true,
    output: true,
  },
})

// ... feed the `schema` into your GraphQL server (e.g. apollo-server or graphql-yoga)

makeSchema also lets you provide a prettier configuration so that the generated code adheres to your style guidelines 💅

Getting started with GraphQL Nexus

The fastest way to get started with Nexus is by exploring the official examples or by using the online Playground.

1) Installation

Since GraphQL Nexus heavily depends on graphql-js, it is required as a peer dependency for the installation:

npm
yarn
npm install --save nexus graphql

2) Configuration & best practices

The best practices section in the docs contains many instructions regarding the ideal editor setup and hints for structuring Nexus projects.

As GraphQL Nexus generates typings on the fly, the best developer experience is achieved with a development server that's running in the background as you code. Whenever you save a file, it takes care of updating the generated typings.

When using TypeScript, one possible setup is to use ts-node-dev for the development server:

npm
yarn
npm install --save-dev ts-node-dev

You can then configure an npm script for development in package.json:

{
  // ...
  "scripts": {
    "start": "...",
    "dev": "ts-node-dev --no-notify --transpileOnly --respawn ./src"
  }
}

When using JavaScript, you can use nodemon:

npm
yarn
npm install --save-dev nodemon

You can then configure an npm script for development in package.json:

{
  // ...
  "scripts": {
    "start": "...",
    "dev": "nodemon ./src/index.js"
  }
}

3) "Hello World" with graphql-yoga

Once you're done with your editor setup, you can start building out your GraphQL schema. Here's what a "Hello World" app with graphql-yoga looks like:

import { queryType, stringArg, makeSchema } from 'nexus'
import { GraphQLServer } from 'graphql-yoga'

const Query = queryType({
  definition(t) {
    t.string('hello', {
      args: { name: stringArg({ nullable: true }) },
      resolve: (parent, { name }) => `Hello ${name || 'World'}!`,
    })
  },
})

const schema = makeSchema({
  types: [Query],
  outputs: {
    schema: __dirname + '/generated/schema.graphql',
    typegen: __dirname + '/generated/typings.ts',
  },
})

const server = new GraphQLServer({
  schema,
})

server.start(() => `Server is running on http://localhost:4000`)

4) Migrating from your SDL-first API

The SDL converter lets you provide an SDL schema definition and outputs the corresponding Nexus code (without any resolvers):


Striving for great developer experience

The Nexus API has been designed with special attention to developer experience. Some core design goals are:

  • Type-safety by default
  • Readability
  • Developer ergonomics
  • Easy integration with Prettier

The development server that's running as you build your API ensures that you always get auto-completion and error checks for the schema changes you just introduced.

With the new schema polling feature in the GraphQL Playground, you GraphQL API will reload instantly as you adjust the schema as well.


Let us know what you think ✍️

We are super excited about GraphQL Nexus and hope that you will be too. Feel free to try out Nexus by exploring the official examples or following the "Getting Started"-instructions in the docs.

If you encounter any problems, please open a GitHub issue or reach out in our Slack.

Comments

Comments

Don’t miss the next post!