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}67type 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'23const Post = objectType({4 name: 'Post',5 definition(t) {6 t.id('id')7 t.string('title')8 t.string('body')9 },10})1112const 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})2627const 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'23const 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 ADMIN3 EDITOR4 PUBLISHER5}
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'23export enum UserRole {4 ADMIN = 'ADMIN',5 EDITOR = 'EDITOR',6 PUBLISHER = 'PUBLISHER',7}89export 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}45type 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'23const Node = interfaceType({4 name: 'Node',5 definition(t) {6 t.id('id', { description: 'GUID for a resource' })7 },8})910const 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.