Code-first GraphQL with Nexus
by Bryan Li, Software Engineer @ Resilia
Much has been written about the merits of typesafe programming languages. But what does it mean to build a GraphQL schema in a typesafe way? What does such a code-first implementation of GraphQL schemas look like? Most importantly, why is it a big deal at Resilia?
Here at Resilia, we are building a platform to empower nonprofit organizations to change the world. We make use of technologies such as GraphQL to interface between our React web application and our backend service. We develop our GraphQL schema using a code-first approach with a library called Nexus. Nexus documentation talks about developing GraphQL schema in a type safe manner, and today we will show what that means in terms of development. In addition, we will expand into integration between Prisma (the data access layer) and Nexus to fully embrace the idea of type safety.
Before moving forward, if you are curious about code-first vs SDL-first approach to develop GraphQL schema, this is a great article to dive into. In addition, discussing our choice of building our database access layer through Prisma is beyond the scope of this article. Here is a great snippet on the benefits of building with Prisma.
Caveat, Nexus went through horribly managed rebranding at the end of 2020. Nexus in here, refers to the latest Nexus library. It is not Nexus Experiment, and it is not Nexus Schema.
Prisma Schema
One of the ways we can help nonprofit organizations is by providing them with modern management tools, such as a tool to manage their budgets. Here is a sample data model that allows us to do that written in prisma schema, the DSL for modeling your database layer using the prisma library.
Database Types
With the prisma.schema file created, we can use the tools provided by prisma to generate the types based on our schema. Whereas in other ORMs we would import the class object, we would do the following to reference the types generated by prisma.
https://gist.github.com/bloo/ad684d69dd2c071406efdd8c65507c37
GraphQL Schema
Now we have our database readily accessible, it is time to build our GraphQL schema.
The code-first approach allows us to start with our resolvers. We can start by designing the interface that can accomplish our goals instead of starting from type definitions. There are additional benefits offered by the Nexus library.
Benefits of Nexus
The most obvious benefit is the ability to organize type definitions and resolvers together. We no longer need to use template literals to build the type definition.
Since Nexus generates types (we will show the set up later) using our code, we can import the GraphQL types anywhere within our code.
However, have you noticed how we create the type definition for NonprofitType? We link the enum from our GraphQL schema directly to the database model. This way, changes in the database model can be directly reflected in our GraphQL schema. What if there is a way to project the types from Prisma to GraphQL schema through Nexus?
This is where the nexus-prisma-plugin comes in. It extends our Prisma type to Nexus. This integration allows us to project the type from the data model (Prisma) to our GraphQL schema. We can rewrite our Budget GraphQL object like this:
This way, we can expose the Prisma models directly to the GraphQL schema. With that being said, there are arguments against this practice. I will let you be the judge on using it in production. It is important to note that it is not necessary to use the nexus-plugin-prisma, even if you are using Prisma as your data access layer. You can still use sourceTypes to allow Nexus to correctly map the type between GraphQL objects to data models (with some caveats explained below).
Putting It Together
Finally we have our GraphQL code and our database layer, it is finally time to put it together using the makeSchema method in the Nexus library. The makeSchema method takes the types we had defined to generate the GraphQL schema for our GraphQL server.
There is a lot to unpack in this file but hopefully the comments can help you navigate the different configurations of the makeSchema method.
One thing you would notice here is that the schema.ts file breaks the convention of the Twelve-Factor Factor App principle that we have embraced here at Resilia by directly accessing the environmental variables. The reason is this file is invoked in two different contexts: pre-build time and runtime. We acknowledge this as tech debt and are investigating ways to make this part of our codebase Twelve-Factor App compliant.
At runtime, this file is executed as vanilla Javascript since we do not use ts-node in production. Therefore, we need to make sure path resolutions will still work after transpilation, and we need to consider the effect of having no Typescript.
At pre-build time, this file is invoked to generate the necessary types for Typescript. Remember that we have been generously importing typegen-nexus to use the type from our GraphQL schema ? Those types need to exist before we build, but they do not exist until we start the server. To solve for that, we have to be able to manually invoke this file.
To overcome the requirements of different contexts, we had to break convention just for this file. With that being said, there is no need to do this if you are using ts-node in production.
GraphQL Server
Having the schema allows us to pass that into our GraphQL server. In addition, we pass an instance of the prisma client into our context object so all resolvers will have access to the data access layer. It is important to note here that it is not the best practice to instantiate a new prisma client on every request.
Common Pitfalls
- We have to make sure Typescript has all the types it needs before transpilation begins if we are deploying transpiled artifacts. Before we run npm build, we need to make sure the proper Prisma and Nexus types are generated.
- Again, do not instantiate a new Prisma Client instance on every request.
- Did I mention do not instantiate a new Prisma Client instance on every request?
Would you like to see an example project using these patterns? Let us know in the comments or reach out to us!