In this chapter you're going to write your first schema. You'll learn about:

  • Writing GraphQL objects
  • Exposing GraphQL objects for query operations
  • GraphQL SDL file generation
  • Enhanced type safety & autocompletion

What about that server?

In the last chapter you probably noticed the minimal setup required to get up and running. In fact, you might even be confused and wondering:

"Hey, all I have is an empty app.ts file, how can my server even be running?"

Well, Nexus comes with a server out of the box. There's no need for you to start or stop the server or otherwise think about it beyond your domain logic. Nexus wants you to focus on what makes your GraphQL API unique.

If your lock-in fears are tingling, know that you still have full access to the underlying server instance. So if you need to add custom middlewares, routes, and so on, you can. It happens that currently it is an express instance but this area of Nexus will evolve (#295).


Before we get going we need a moment to introduce an important part of the Nexus development workflow. Nexus has an unconventional concept called "Reflection". It refers to the fact that, when nexus dev or nexus build is running, not only is your application code being run, but information is being gathered and artifacts are being derived. Some of Nexus' uses for reflection include:

  • Figuring out which plugins you are using, and the settings passed
  • Generating TypeScript types to give your resolvers complete type safety
  • Generating an SDL file

This partly explains why Nexus has a declarative API. It needs a way to run your app reliably at build time. Declarative APIs give Nexus a higher degree of control to do this. Declarative APIs also encode enough semantic value for Nexus to do the things it needs to.

Architecturally there's a lot more to say about reflection but for now, from a user point of view, just remember the following. You should always have your nexus dev running when you're working on your project even when you're not intending to use your server (e.g. access the GraphQL Playground). If you forget to run nexus dev then you will not, for example, get the static typing experience that you expect in your resolvers.

There are plans to run Nexus Reflection as a separate process integrated into your IDE. You can learn more about and track the feature here (#949)

Model the domain

Let's get started with our blog schema by modeling some key entities in the domain. We'll begin with the concept of a Post. A post will represent a page in your blog. When not published, we'll call it a draft.

Your modeling work is going to start on the API layer as opposed to the database layer. This API-first approach can be a good way to collaborate with frontend teams, getting their input in shaping the data early.

A note about terminology. We will be talking about the Post Object, not the Post Model. The difference is that at the API layer we have objects but at the database layer we have models. The name difference helps us talk about these different layers without confusion. It is also how GraphQL (API layer) and Prisma (database layer, discussed later) respectively refer to these things.

Create a new module for your Post object at api/graphql/Post.ts. We could write our whole schema within say api/app.ts or api/graphql.ts, but modularizing your GraphQL type definitions can help scale your codebase. Neither approach is inherently wrong though, so do as you see you fit. For this tutorial we'll use the modular style.

$mkdir api/graphql && touch api/graphql/Post.ts

To create the Post object we'll import the schema component from the nexus package. schema is where you'll find all the building blocks to craft your GraphQL types. Right now we are interested in the schema.objectType method, which, unsurprisingly, helps building GraphQL Object Types.

1// api/graphql/Post.ts
3import { schema } from 'nexus'
6 name: 'Post', // <- Name of your type
7 definition(t) {
8 t.int('id') // <- Field named `id` of type `Int`
9 t.string('title') // <- Field named `title` of type `String`
10 t.string('body') // <- Field named `body` of type `String`
11 t.boolean('published') // <- Field named `published` of type `Boolean`
12 },


Once you've saved this file change to disk, your app will be restarted and the previous warning you had about an empty GraphQL schema should be gone.

You may notice that there's also now a new api.graphql file at your project root. It contains a representation of your schema in a syntax called the GraphQL Schema Definition Language (SDL for short). In dev mode Nexus generates this for you at every app restart. In it you should see the following:

1type Post {
2 id: Int
3 title: String
4 body: String
5 published: Boolean

You are free to disable this file (settings discussed later) but its existence has two benefits for you to consider:

  1. For users familiar with SDL the correspondence between the source code and it may help them learn Nexus' schema API faster.
  2. The SDL syntax makes it an accessible way for others to evaluate incoming API changes without having to know about Nexus, or even JavaScript. Consider using the generated SDL file to improve your pull-request reviews.

For the remainder of this tutorial we'll be keeping SDL to the right of Nexus code blocks.

Your first home-grown query

Your Post object is in place now but there's still no way for clients to read that data. Let's change that. You'll use the special Query object to expose your Post object.

We'll start by letting API clients read the drafts of your blog.

1// api/graphql/Post.ts // 1
2// ...
5 type: 'Query', // 2
6 definition(t) {
7 t.field('drafts', { // 3
8 nullable: false, // 4
9 type: 'Post', // 5
10 list: true, // 6
11 })
12 },
1type Query {
2 drafts: [Post!]!
  1. The Query object is a central place in your schema where many other types will appear. Like before with the modular GraphQL types decision we again can decide to be modular here. We could either create a new api/graphql/Query.ts module (not modular), or we could collocate the exposure of Post object with its definition in api/graphql/Post.ts (modular). Staying consistent with before, we'll take the modular way.

  2. To achieve collocation in Nexus we'll use schema.extendType. Its API is very similar to schema.objectType with the difference that the defined fields are merged into the targeted type.

  3. The first parameter specifies the field's name, here drafts

  4. nullable: false specifies that clients will always get a value for this field. By default, in Nexus, all "output types" (types returned by fields) are nullable. This is for best practice reasons. In this case though we indeed want to guarantee that a list will always be returned, never null.

    If you're ever dissatisfied with Nexus' defaults, not to worry, you can change them.

  5. type: 'Post' specifies what the field's type should be. Here, a Post

  6. list: true augments the field's type spec, making it wrapped by a List type. Here, a [Post]. Nexus also provides the following shorthand for this 👇

    1definition(t) {
    2 t.list.field('drafts', { ... })

You'll see some feedback from your IDE that you're missing a resolve property. Go ahead and try to implement it, letting the autocompletion guide you.

You might be wondering why Nexus hasn't complained about missing resolvers in some other cases so far. The answer is a more advanced topic that we'll cover later.

1// api/graphql/Post.ts
2import { schema } from 'nexus'
3// ...
6 type: 'Query',
7 definition(t) {
8 t.field('drafts', {
9 type: 'Post',
10 list: true,
11 resolve() {
12 return [{ id: 1, title: 'Nexus', body: '...', published: false }]
13 },
14 })
15 },

Try it out

You can now open up your GraphQL playground and try the following query (left); In response, you should see something like so (right):

2 drafts {
3 id
4 title
5 body
6 published
7 }
2 "data": {
3 "posts": [
4 {
5 "id": 1,
6 "title": "Nexus",
7 "body": "...",
8 "published": false
9 }
10 ]
11 }

Wrapping up

Congratulations! You've successfully got your first GraphQL schema up and running with Nexus! In the next chapter we'll explore adding some write capabilities to our API.

Next →
Edit this page on Github