Getting started / Tutorial
2. Writing your first schema
Overview
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
Reflection?
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 makeSchema
is being called, 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:
- 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.
It also explains the need for the --transpile-only
flag passed to ts-node-dev
. If you don't know what it is about, it basically tells TypeScript not to typecheck your app. Since nexus
needs to be run in order to generate TypeScript types, we need to ensure that nexus
won't ever be prevented from generating these types because of a type error.
You might be (rightfully) wondering: "Wait, how am I supposed to benefit from Nexus' type-safety if I disable it in ts-node-dev
... ?". For now, the answer to that is to use your IDE's type-checker. If your IDE/terminal doesn't have one, then manually run an additional tsc
process.
Just remember the following though. You should always have your npm run dev
script 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 npm run dev
then you will not, for example, get the static typing experience that you expect in your resolvers.
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/schema.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.
$
To create the Post
object we'll import the objectType
function from the nexus
package. This will help you building a GraphQL Object Types.
1// api/graphql/Post.ts23import { objectType } from 'nexus'45export const Post = objectType({6 name: 'Post', // <- Name of your type7 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 },13})
Additionally, we need to pass that Post
object type down to our makeSchema
function. To achieve that, we'll create an api/graphql/index.ts
file, which will be used as an index to re-export all types from your schema.
1// api/graphql/index.ts23export * from './Post'
Finally, we'll import this file and pass it down to makeSchema
1// api/schema.ts2import { makeSchema } from 'nexus'3import { join } from 'path'+ import * as types from './graphql'56const schema = makeSchema({- types: []+ types,9 outputs: {10 typegen: join(__dirname, '../nexus-typegen.ts'),11 schema: join(__dirname, '../schema.graphql')12 }13})
Note: It is considered best practice to pass your types directly from a "star import" like we've done above. Under the hood, Nexus will unwrap the types. This prevents from constantly having to manually export & import every single type of your API.
SDL?
Once you've saved this file change to disk, your app will be restarted.
You may notice that there's also now a new schema.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 body: String3 id: Int4 published: Boolean5 title: String6}
You are free to disable this file (settings discussed later) but its existence has two benefits for you to consider:
- For users familiar with SDL the correspondence between the source code and it may help them learn Nexus' schema API faster.
- 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.
NOTE: This should cause a static type error when you save the file. Don't worry, we'll fix it in a second!
1// api/graphql/Post.ts // 123import { extendType } from 'nexus'45export const PostQuery = extendType({6 type: 'Query', // 27 definition(t) {8 t.nonNull.list.field('drafts', { // 3, 4, 59 type: 'Post', // 6, 710 })11 },12})
1type Query {2 drafts: [Post]!3}
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 inapi/graphql/Post.ts
(modular). Staying consistent with before, we'll take the modular way.To achieve collocation in Nexus we'll use
schema.extendType
. Its API is very similar toschema.objectType
with the difference that the defined fields are merged into the targeted type..nonNull
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, nevernull
. If you're ever dissatisfied with Nexus' defaults, not to worry, you can change them..list
augments the field's type spec, making it wrapped by a List type. Here, a[Post]
.The first parameter specifies the field's name, here
drafts
type: 'Post'
specifies what the field's type should be. Here, aPost
Nexus also allows you to specifiy lists and nullability on the type field. This example could be rewritten like so 👇
1t.field('drafts', {2 type: nonNull(list('Post')),3})
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.ts2import { extendType } from 'nexus'3// ...45export const PostQuery = extendType({6 type: 'Query',7 definition(t) {8 t.nonNull.list.field('drafts', {9 type: 'Post',+ resolve() {+ 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):
12345678
1{2 "data": {3 "drafts": [4 {5 "id": 1,6 "title": "Nexus",7 "body": "...",8 "published": false9 }10 ]11 }12}
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 →