NestJS + GraphQL
- GraphQL + TS = better type safety when writing GraphQL queries, end-to-end typing.
<!– - We use Apollo server. To do that we need
@nestjs/apollo. -
We will use Mercurius which uses Fastify to implement:
- GraphQL servers.
- Gateways.
For this we need
@nestjs/mercurius.
[!NOTE]
NestJS says in its doc that they provide official integrations for these GraphQL packages. That’s good enough for me to pick these libs.
–>
Schema first VS Code first
Nest offers both ways of building GraphQL applications, you can learn which one works best for you here, and here. Regardless of which method you use, the GraphQL playground shows the schema. My conclusion:
Schema first:
- Write your GraphQL API via SDL.
- Parallelize frontend & backend development.
- Resembles TDD: Think about the API rather than the implementation.
- Easy to read.
- Keep resolvers and schema in sync manually.
- It relies on default resolvers.
Code first:
- Generates the SDL from your code.
- Use decorators and classes to generate the corresponding GraphQL schema.
- Single source of truth.
- Generating the schema can be done automatically as part of our CI.
- Legibility: Types can be ordered alphabetically.
- Can be committed to another repo like what GH does (ref).
- Enables us to modularize schema definition.
- Better dev exp.
- Can do better on localization, related issue in GraphQL spec GH repo.
- Generates the SDL from your code.

Bootstrap your NestJS backend in Nx
-
pnpm i @nestjs/graphql @nestjs/apollo @apollo/server graphql -
nx g @nx/node:app --framework nest --directory apps/todo-nest - Update your
tsconfig*.jsonfiles to useNodeNextinstead ofCommonJS. -
nx serve todo-nest --configuration=production - Now you should be able to start developing your dream GraphQL in NestJS.
- You’re GraphQL IDE is available at
http://localhost:${PORT}/graphql. Note that this is not GraphiQL. But it is their Apollo’s own IDE. - By default,
GraphQLModulesearches for resolvers throughout the whole app. To limit this scan to only a subset of modules, use theincludeproperty.
- You’re GraphQL IDE is available at
[!NOTE]
It seems that NestJS does not like the idea of using esbuild, or at least we cannot use it without a lot of troubles.
— Ref.
Create objects/mutations/queries
- Objects are what we return as a response.
- An object maps to a domain object.
- Domain has no relation to DDD.
- Domain refers to the specific area or subject matter that your application deals with. In other words, it’s the real-world concepts and entities that your application models and interacts with.
- We need objects since our client needs to interact with those domains.
Query
To write our queries we usually need a new object in our GraphQL API. Here is what you need to do in a code first approach:
-
cd apps/todo-nest -
nest g module user nest g module todoI know it sucks but I did not find a better solution to use Nx’s integrated CLI for NestJS.
-
nest g class user/models/user --no-spec nest g class todo/models/todo --no-specThen rename it to
user.model.tsand move it + its test file one level up. Change thetodotoo. - Change the newly created models the way you wanted them to be.
-
nest g class user/user nest g class todo/todoMove them again one level up, and rename them to
todo.resolver.tsandTodoResolver. Repeat the same thing for user. -
nest g service user/userAnd one more for repository, but you need to rename it so that is correct.
-
nx g @nx-tools/nx-prisma:configuration --project todo-nest --database postgresql nx g @nx/js:lib libs/sharedIn that shared lib you can add your prisma client service.
[!TIP]
# After a lot of back and forth regarding how to query nested fields in GraphQL I reached a conclusion. Let’s start by explaining how we resolve nested fields in GraphQL:
- We have
@ResolveFieldin NestJS to declare a resolver for a nested field.- Then we also have
@Queryto annotate our main resolver (or what we call our query).So I was thinking that ah, this sucks because I’m making one or more than one extra unnecessary data fetching from database. I mean I could easily do something like this:
prisma.todo.findFirst({ where: { id }, include: { AssignedTo: true, CreatedBy: true }, });This is commonly known as
N+1issue in GraphQL. I am not sure except writing raw queries or the new feature of Prisma what else we can do since:
- ORMs make separate queries to fetch data and then at the application level they put them together and shave it to your taste (this is how things are usually done when you use their APIs).
- The raw SQL query even for the most basic thing would be really hard to maintain. Just look at this query for fetching one todo + who has created it and if it is being assigned to someone, I want their user info too.
- Another important thing here is to know that we are not modular at all. I mean this
@ResolveFieldcan resolve any field with that name and the same return type.- If the query is really crazy heavy then we have to write raw queries then. So we also will take care of all maintenance it brings along.
- In general it is a good idea to use
@paljssince it can help us a lot with reducing the amount of data being transferred from database to our NodeJS app.- This new feature of Prisma where they use
LATERAL JOINis not flawless either (learn more).# Conclusion: I am gonna use
@paljs/pluginfor the foreseeable future :smile:. But keep in mind that there are scenarios that it might requires us to write extra code, e.g.:
An external Auth provider handles users data, therefore our app needs to fetch data from their API in a separate
@ResolveFieldmethod:query { post { id title # Assume user info is stored in FusionAuth. user { id name } } }Or when your pagination is following “Connections” spec. Then your query might not match exactly the underlying database. Learn more here.
[!TIP]
Create separate NestJS modules for each domain model.
- “Domain model”:
- Conceptual model of specific domain of our app or its problem space.
- A representation of the concepts, rules, and logic of a specific business domain.
- E.g., in an ECommerce app, entities such as
Product,Order,Customer, andInventoryare domain models.- Structuring your codebase around:
- The core business logic.
- Or areas of functionality in your application.
[!TIP]
One thing that I really love about
@nestjs/graphqlis that it exports anGraphQLISODateTimewhich we can use to signify the type of date fields:import { ObjectType, GraphQLISODateTime, Field, } from '@nestjs/graphql'; @ObjectType('EntityName') export class EntityName { @Field(() => GraphQLISODateTime) createdAt!: Date; }
Mutation
To change data on the server you need to create an Input object type most of the times (when the data you receive is complex) and an Object to show response schema:
- Write one E2E test for it.
-
Then start with implementation:
- Use
@Mutationdecorator to annotate the method which will serve as your resolver. -
cd apps/todo-nest && my-touch src/todo/dto/create-todo-input.dto.tsTo create a DTO. BTW if you do not know what does
my-touchyou can read this. - Write your service layer and repository layer logic for the mutation and then wire everything up.
- Use
Query Complexity
[!CAUTION]
- First make sure to read this doc about cost analysis.
- Only works in code first approach.
- Define how complex certain fields are.
- A common default is to give each field a complexity of 1.
- # Calculate the complexity of a query via a “complexity estimator”;
- A simple function that calculates the complexity of a field.
- Add any number of complexity estimators to the rule, which are then executed one after another.
- The first estimator that returns a numeric complexity value determines the complexity for that field.
- Restrict queries with a maximum complexity to prevent attack such as DoS.
- Her we’re gonna use a 3rd-party packages called
graphql-query-complexity.
graphql-query-complexity
[!NOTE]
You can see the final NestJS app with query complexity configured here:
apps/complexity. Also I wrote a post for this topic on dev.to.
-
pnpm add graphql-query-complexity - Follow step #1 through #5.
-
nest g cl complexity-plugin --flat - Then write a custom “plugin” for your apollo server to calculate the complexity of a query.
- Add the newly created class to
providersarray. - Now we can utilize:
- The
complexityfield on a DTO. - Or pass a complexity estimator function.
- The
[!CAUTION]
Issue: If you have nested fields and use
@ResolveFielddecorator it is not gonna add up all the nested queries. So you’ll end up with whatever your return from your cost estimator or the hard coded value (like what we’re doing here, here, here, and here).And our query is this:
query { getPosts { id author { posts { id author { posts { id author { posts { id author { id } } } } } } } } }Although there is one possible fix that I had done here. Note, you can find an enhanced version of
apps/complexityinapps/dataloader-examplewhich reduced the amount of time needed for this query from ~5.5 seconds to ~3.7 seconds:{ getPosts { id author { id posts { id } } } }more on that here.
[!CAUTION]
I also cannot see how you might be doing field cost analysis separately from type cost analysis and add them together to figure out the cost for a query.