Getting started

Why Nexus?

Schema-First GraphQL APIs

There's a common path that most GraphQL API developers take when they get started: define a schema with the Schema Definition Language and write resolver logic to furnish data.

A simple schema might look like this:

1type Post {
2 id: ID!
3 title: String!
4 body: String!
5}
6
7type Query {
8 posts: [Post]!
9}

The corresponding resolver logic would then be supplied to allow the API to serve data:

1const Query = {
2 posts: () => [
3 {
4 id: '1',
5 title: 'My first GraphQL server',
6 body: 'How I wrote my first GraphQL server',
7 },
8 ],
9}

The schema type definitons, along with the resolvers, would then be passed to something like ApolloServer to create and serve the API.

This way of building a GraphQL server is often referred to as the "schema-first" approach. We start by defining the shape of our API and then write code to tell it how to return data.

While the schema-first approach is easy to get started with, it comes with some inherent drawbacks that can make development difficult when applications start getting bigger.

Nexus takes a different approach to building GraphQL servers. Instead of keeping a separate schema and set of resolvers, with Nexus we write both our schema and resolvers in the same spot using code.

The Code-First Approach

Writing a GraphQL server with Nexus differs starkly to the traditional schema-first approach. With Nexus, we write our schema and resolver logic all in one place with a common language (JavaScript/TypeScript).

Refactoring the Post example above to Nexus would look like this:

1import { objectType, queryType, makeSchema } from 'nexus'
2
3const Post = objectType({
4 name: 'Post',
5 definition(t) {
6 t.id('id')
7 t.string('title')
8 t.string('body')
9 },
10})
11
12const Query = queryType({
13 definition(t) {
14 t.list.field('posts', {
15 type: "Post",
16 resolve: () => [
17 {
18 id: '1',
19 title: 'My first GraphQL server',
20 body: 'How I wrote my first GraphQL server',
21 },
22 ],
23 })
24 },
25})
26
27const schema = makeSchema({
28 types: [Post, Query],
29})

The benefits of taking a code-first approach to GraphQL APIs isn't always apparent at first glance. However, the benefits are realized over time, especially as application and team size grows.

There are numerous benefits to taking a code-first approach with Nexus:

Schema and Resolver Co-location

When building a schema-first GraphQL API, it is common to start out by placing all type definitions and resolvers in a single file. When both the schema and the resolvers live next to one another, it's fairly straightforward to work in both at the same time.

As the application grows, however, it is most often desired to move parts of the schema into their own separate modules and files. It's at this point that working on a GraphQL API becomes a bit more tedious. With this modularization comes the need to switch back and forth between the Schema Definition Language and JavaScript/TypeScript to write the resolvers. Not only does one need to constantly switch between files, they also need to do a context switch mentally to work between the two languages.

With Nexus, our schema and its resolvers are always defined together. Nexus also allows us to write everything in a common language. This allows us to side-step the co-location/context switching issue altogether and helps us to be more productive, even as our applications grow to be quite large.

Automatic Type and Schema Definition Language Generation

One major benefit of using Nexus is its ability to automatically generate TypeScript types and GraphQL Schema Definition Language (SDL) files. The generated types are useful for adding extra type safety to the code used to power your GraphQL API. The generated SDL files can be used for many purposes. For example, we can configure our editors to know about the shape of our APIs to give us introspection for the queries and mutations we write.

Type and SDL generation comes for free with Nexus and can be enabled by supplying some configuration in the makeSchema call.

1import path from 'path'
2
3const schema = makeSchema({
4 types: [Post, Query],
5 outputs: {
6 schema: path.join(__dirname, 'generated/schema.gen.graphql'),
7 typegen: path.join(__dirname, 'generated/nexusTypes.gen.ts'),
8 },
9})

Fast Feedback Loop

Thanks to the automatically generated TypeScript types Nexus is able to integrate seamlessly into your editor and provides a fast feedback loop.

Whenever makeSchema generates the TypeScript types your editor receives instant feedback on whether your implementation fullfills the schema requirements (even if you are not a TypeScript user). This allows you spot issues early on, such as:

  • Nullability checks
  • A resolver is missing
  • A model type doesn't fulfill the schema type

By having the server restart on file changes this feedback is given almost instantly as changes are made.

Reduce repetition

A downside of the schema-first approach is the need to repeat yourself in schema definition language and in code.

Defining enums

When defining an enum using the schema-first approach, the enum must first be defined in the schema definition language:

1enum UserRole {
2 ADMIN
3 EDITOR
4 PUBLISHER
5}

This has to be repeated in the implementation:

1export enum UserRole {
2 ADMIN = 'ADMIN',
3 EDITOR = 'EDITOR',
4 PUBLISHER = 'PUBLISHER',
5}

Keeping both of these in sync is tedious and error phrone, especially when files are not co-located together.

Refactoring the above to Nexus would look like this:

1import { enumType } from 'nexus'
2
3export enum UserRole {
4 ADMIN = 'ADMIN',
5 EDITOR = 'EDITOR',
6 PUBLISHER = 'PUBLISHER',
7}
8
9export const UserRoleEnum = enumType({
10 name: 'UserRole',
11 members: UserRole,
12})

The enum values are only defined once: in the model.

Implementing Interfaces

Interfaces are a standard feature of GraphQL. When using the schema-first approach, interfaces require a lot of repetition:

1interface Node {
2 id: ID!
3}
4
5type Post implements Node {
6 id: ID!
7 title: String!
8 body: String!
9}

The above example shows a Node interface and a Post interface implementing it.

The schema definition languange requires that the Post type repeats all the fields from the Node interface, as a schema becomes bigger and more interfaces and types are added this quickly becomes tedious.

With Nexus the repetitive work is kept to a bare minimum:

1import { interfaceType } from 'nexus'
2
3const Node = interfaceType({
4 name: 'Node',
5 definition(t) {
6 t.id('id', { description: 'GUID for a resource' })
7 },
8})
9
10const Post = objectType({
11 name: 'Post',
12 definition(t) {
13 t.implements('Node')
14 t.string('title')
15 t.string('body')
16 },
17})

Build your own abstraction

Enums and Interfaces are built-in building blocks of Nexus, but it doesn't stop here.

Thanks to Nexus' rich plugin system it is possible to build more complex abstractions to improve code re-use, an example of this is the official connection plugin to help build relay style pagination.

No Need for Extra Tooling

When writing a schema-first GraphQL API, it's often necessary to install and configure several editor extentions and other extra tooling. This is because the Schema Definition Language needs to be properly understood by out editors to be useful.

Nexus offers the benefit of providing a great developer experience without the need for any extensions or extra tooling. It also doesn't require developers to learn the Schema Definition Language to be productive. This combination means that JavaScript/TypeScript developers can be added to a project and be successful faster.

Edit this page on Github