GraphQL

Write an awesome doc for GraphQL. A very nice an practical one extracted from GraphQL official documentation.

View on GitHub

NestJS + GraphQL

[!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 VS Code first in picture

Bootstrap your NestJS backend in Nx

  1. pnpm i @nestjs/graphql @nestjs/apollo @apollo/server graphql
    
  2. nx g @nx/node:app --framework nest --directory apps/todo-nest
    
  3. Update your tsconfig*.json files to use NodeNext instead of CommonJS.
  4. nx serve todo-nest --configuration=production
    
  5. 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, GraphQLModule searches for resolvers throughout the whole app. To limit this scan to only a subset of modules, use the include property.

[!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

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:

  1. cd apps/todo-nest
    
  2. nest g module user
    nest g module todo
    

    I know it sucks but I did not find a better solution to use Nx’s integrated CLI for NestJS.

  3. nest g class user/models/user --no-spec
    nest g class todo/models/todo --no-spec
    

    Then rename it to user.model.ts and move it + its test file one level up. Change the todo too.

  4. Change the newly created models the way you wanted them to be.
  5. nest g class user/user
    nest g class todo/todo
    

    Move them again one level up, and rename them to todo.resolver.ts and TodoResolver. Repeat the same thing for user.

  6. nest g service user/user
    

    And one more for repository, but you need to rename it so that is correct.

  7. nx g @nx-tools/nx-prisma:configuration --project todo-nest --database postgresql
    nx g @nx/js:lib libs/shared
    

    In 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 @ResolveField in NestJS to declare a resolver for a nested field.
  • Then we also have @Query to 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+1 issue in GraphQL. I am not sure except writing raw queries or the new feature of Prisma what else we can do since:

  1. 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).
  2. 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.
  3. Another important thing here is to know that we are not modular at all. I mean this @ResolveField can resolve any field with that name and the same return type.
  4. 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.
  5. In general it is a good idea to use @paljs since it can help us a lot with reducing the amount of data being transferred from database to our NodeJS app.
  6. This new feature of Prisma where they use LATERAL JOIN is not flawless either (learn more).

# Conclusion: I am gonna use @paljs/plugin for 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 @ResolveField method:

    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, and Inventory are 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/graphql is that it exports an GraphQLISODateTime which 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:

  1. Write one E2E test for it.
  2. Then start with implementation:

    1. Use @Mutation decorator to annotate the method which will serve as your resolver.
    2. cd apps/todo-nest && my-touch src/todo/dto/create-todo-input.dto.ts
      

      To create a DTO. BTW if you do not know what does my-touch you can read this.

    3. Write your service layer and repository layer logic for the mutation and then wire everything up.

Query Complexity

[!CAUTION]

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.

  1. pnpm add graphql-query-complexity
    
  2. Follow step #1 through #5.
  3. nest g cl complexity-plugin --flat
    
  4. Then write a custom “plugin” for your apollo server to calculate the complexity of a query.
  5. Add the newly created class to providers array.
  6. Now we can utilize:
    • The complexity field on a DTO.
    • Or pass a complexity estimator function.

[!CAUTION]

Issue: If you have nested fields and use @ResolveField decorator 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/complexity in apps/dataloader-example which 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.