DEV Community

Marc Stammerjohann for notiz.dev

Posted on • Originally published at notiz.dev on

GraphQL Code-First Approach with NestJS 7

Recently the release of NestJS 7 was announced with amazing updates to the whole framework including the @nestjs/graphql ❀️ package.

We create a GraphQL API using the @nestjs/graphql. You will learn how to write the API with TypeScript using the code first approach and the new GraphQL plugin.

In this guide we are using Prisma to easily access a database. You can follow this guide to setup a Nest application with Prisma as Prisma is out of scope for this guide.

Setup GraphQL

To start a GraphQL API install the following packages into your Nest application.

npm i --save @nestjs/graphql graphql-tools graphql

# for Express
npm i --save apollo-server-express
# for Fastify
npm i --save apollo-server-fastify

Import the GraphQLModule into your AppModule.

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';

@Module({
  imports: [
    GraphQLModule.forRoot({
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
      debug: true,
      playground: true
    })
  ]
})
export class AppModule {}

To configure the GraphQL endpoint we use GqlModuleOptions which are passed to the underlying GraphQL server. Here we are enabling the code first approach.

  • autoSchemaFile enables the code first approach to use TypeScript classes and decorators to generate the GraphQL schema.
  • playground enables the GraphQl Playground, an interactive IDE for your API documentation, available at http://localhost:3000/graphql.
  • debug mode

There are two options for autoSchemaFile providing a path for the schema generation or true for generating the schema in memory.

GraphQL Code First approach

A GraphQL schema contains many types and Queries. The schema grows in size and complexity for each new query, mutation and type. GraphQL Code First enables us to automatically generate a GraphQL schema using TypeScript and decorators. This helps us focus on writing .ts files and we don't need to write the GraphQL schema ourselves.

@nestjs/graphql provides all decorators to generate our schema. Here are a few decorators and there usage:

  • @ObjectType() generate class as Type
  • @Field() generate a class property as a Field
  • @InputType() generate class as Input
  • @Args generate method params as Arguments
  • @Query() generate method as Query
  • @Mutation() generate method as Mutation
  • @ResolveField resolve relationship property

Graphql Type

Start with creating your objects as a TypeScript class.

export class User {
  id: number;
  createdAt: Date;
  updatedAt: Date;
  email: string;
  password: string;
  name?: string;
  hobbies: Hobby[];
}

export class Hobby {
  id: number;
  name: string;
}

Let's add decorators to expose this model in our GraphQL schema. Start adding @ObjectType() to the TypeScript class.

import { ObjectType } from '@nestjs/graphql';

@ObjectType()
export class User {
  ...
}

@ObjectType()
export class Hobby {
  ...
}

Next we use the @Field decorator on each class property providing additional information about the type and state (required or optional).

import { ObjectType, Field, Int } from '@nestjs/graphql';

@ObjectType()
export class User {
  @Field(type => Int)
  id: number;

  @Field(type => Date, { name: 'registeredAt' })
  createdAt: Date;

  @Field(type => Date)
  updatedAt: Date;

  @Field(type => String)
  email: string;

  password: string;

  @Field(type => String, { nullable: true })
  name?: string;

  @Field(type => [Hobby])
  hobbies: Hobby[];
}

@ObjectType()
export class Hobby {
  @Field(type => Int)
  id: number;

  @Field(type => String)
  name: string;
}

The following GraphQL type is generated if this class is used in a resolver.

type User {
  id: Int!
  registeredAt: DateTime!
  updatedAt: DateTime!
  email: String!
  name: String
  hobbies: [Hobby!]!
}
  • @Field takes an optional type function (e.g. type => String)
  • Declare a field as an array using the bracket notation [] in the type function (e.g. type => [Hobby])
  • Optional FieldOptions object to change the generated schema
  • name: property name in the schema (createdAt => registeredAt)
  • description: adding a field description
  • deprecationReason: adding a deprecation notice
  • nullable: declare a field is required or optional
  • Hide properties from the schema by omitting @Field

For more details head over to the NestJS docs!

We have added a bit of boilerplate to our User model and other models we will create. Nest provides a CLI plugin to reduce the boilerplate of our models. Check out the GraphQL plugin section on how to reduce the boilerplate.

GraphQL Resolver

Great our models are in place! Now we use the Nest CLI to generate our resolvers.

nest generate resolver <name>

# alias
nest g r <name>

# User and Hobby
nest g r user
nest g r hobby

Our resolvers are added to the providers array in the app.module.ts.

import { Resolver } from '@nestjs/graphql';
import { User } from '../models/user.model';

@Resolver(of => User)
export class UserResolver {
  ...
}

Declare a of function in the @Resolver decorator (e.g. @Resolver(of => User)) this is used to provide a parent object in @ResolveField. We will cover @ResolveField in a bit.

Add @Query to your resolvers to create new GraphQL queries in your schema. Let's create a query function returning all users(). Use the bracket notation inside the decorator @Query(returns => [User]) to declare an array return value.

Note: Prisma is used in this example, but can be replaced easily with an ORM of your choice like TypeORM, Mongoose or Sequelize. See the full database setup in the example repo.

import { Resolver, Query } from '@nestjs/graphql';
import { User } from '../models/user.model';
import { PrismaService } from '../prisma/prisma.service';

@Resolver(of => User)
export class UserResolver {
  constructor(private prisma: PrismaService) {}

  @Query(returns => [User])
  async users() {
    return this.prisma.user.findMany();
  }
}

The above code generates the following query to our schema:

type Query {
  users: [User!]!
}

A User has a relation to many hobbies. To resolve the hobbies property from a user, we make use of the @ResolveField decorator. Add @ResolveField to a function with the exact same name of the property we want to resolve. Here we add a hobbies() function and provide a User object as the parent.

import { Resolver, Query, ResolveField, Parent } from '@nestjs/graphql';
import { User } from '../models/user.model';
import { PrismaService } from '../prisma/prisma.service';

@Resolver(of => User)
export class UserResolver {
  constructor(private prisma: PrismaService) {}

  @Query(returns => [User])
  async users() {
    return this.prisma.user.findMany();
  }

  @ResolveField()
  async hobbies(@Parent() user: User) {
    return this.prisma.hobby.findMany({
      where: { user: { id: user.id } }
    });
  }
}

Use the parent object to query the relationship object from a database or another endpoint.

Test GraphQL API

Start your Nest application and navigate to the playground, it is available if playground is set to true in the GraphQLModule.

The playground shows us our GraphQL schema and the docs for our queries.

Graphql Playground schema view

Additionally, we can "play" with queries inside the playground. Try out the autocomplete feature in the playground to create your own queries based on your schema and queries.Let's query all users using the following query:

query AllUsers {
  users {
    id
    registeredAt
    updatedAt
    email
    name
    hobbies {
      id
      name
    }
  }
}

The response will look like this with a different data set. I prepared the database with a few dummy users and hobbies.

Users query

GraphQL plugin

Nest 7 provides a new GraphQL plugin to reduce the boilerplate of decorators for our models , inputs , args and entity files. Enable the plugin by adding compilerOptions to nest-cli.json:

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": ["@nestjs/graphql/plugin"]
  }
}

The plugin automatically handles the decorators for the files with the suffix ['.input.ts', '.args.ts', '.entity.ts', '.model.ts']. If you like to use custom suffixes add those to the plugins option:

"plugins": [
  {
    "name": "@nestjs/graphql/plugin",
    "options": {
      "typeFileNameSuffix": [".input.ts", ".model.ts"]
    }
  }
]

Let's clean up the boilerplate of our models. Before the plugin the models look like this:

import { ObjectType, Field, Int } from '@nestjs/graphql';
import { Hobby } from './hobby.model';

@ObjectType()
export class User {
  @Field(type => Int)
  id: number;

  @Field(type => Date, { name: 'registeredAt' })
  createdAt: Date;

  @Field(type => Date)
  updatedAt: Date;

  @Field(type => String)
  email: string;

  password: string;

  @Field(type => String, { nullable: true })
  name?: string;

  @Field(type => [Hobby])
  hobbies: Hobby[];
}

@ObjectType()
export class Hobby {
  @Field(type => Int)
  id: number;

  @Field(type => String)
  name: string;
}

After removing the extra boilerplate decorators the models looks like this:

import { ObjectType, Field, Int, HideField } from '@nestjs/graphql';
import { Hobby } from './hobby.model';

@ObjectType()
export class User {
  @Field(type => Int)
  id: number;

  @Field({ name: 'registeredAt' })
  createdAt: Date;

  updatedAt: Date;

  email: string;

  @HideField()
  password: string;

  name?: string;

  hobbies: Hobby[];
}

@ObjectType()
export class Hobby {
  @Field(type => Int)
  id: number;

  name: string;
}

Note: Hiding properties from the schema requires the @HideField decorator.

We can add @Field to any property to override the documentation and also the inferred type.For example number is inferred as the GraphQL type Float here we can use @Field(type => Int) to change this to an Int type.

Top comments (13)

Collapse
 
dadooda profile image
Alex Fortuna

Hey Marc,

Great post, thank you. There's a question many people ask, although it's not that easy to find answers for:

How can we (if we can) document the GraphQL schema (fields, queries, mutations) using code first approach?

I use code first, but I also want "DOCS" be more meaningful in the playground. If you know it and you can demo it in your examples, that'd be insanely awesome.

Cheers,
Alex

Collapse
 
marcjulian profile image
Marc Stammerjohann • Edited

Hi Alex,

you can add a description option to the code first decorators @Query, @Mutation, @ObjectType, @Field and more.

Here is an example how to add a description:

@ObjectType({ description: 'Authentication Payload' })
export class Auth {
  @Field({ description: 'JWT Bearer Token for Authentication' })
  token: string;

   ....
}

This documentation is added to the generated graphql schema as comment """DESCRIPTION""" to the field or query, this is getting picked up by the graphql playground.

Let me know if this helps you.

Collapse
 
dadooda profile image
Alex Fortuna

Hey Marc,

Thank you for the info.

The recipe seems to work, just tried it in NestJS 7. Apollo's GraphQL Playground doesn't make descriptions quite visible though. But they are there (the "DOCS" tab).

The set of supported markups is yet to be discovered. Markdown's code seems to work, don't know about the rest.

Cheers!

Thread Thread
 
marcjulian profile image
Marc Stammerjohann

Good idea to add markdown to the docs that helps too.

I am glad I could help :)

Collapse
 
thavoo profile image
Gustavo Herrera

Nice, thanks for you post.

Collapse
 
marcjulian profile image
Marc Stammerjohann

Thank you!

Collapse
 
humayunkabir profile image
Humayun Kabir

I am facing a weird problem. I am using @nestjs/graphql/plugin. Sometimes It generates the schema properly and sometimes it doesn't. Is there any issue with this plugin?

Collapse
 
marcjulian profile image
Marc Stammerjohann

Hi Humayun, can you provide a repo for me to reproduce? Maybe two eyes more can find the problem. I have not found any issues with the plugin until now.

Collapse
 
mark600211 profile image
mark600211

and the main question. can I use schema.gql file auto generated when I use code first approach as schema.prisma???

Collapse
 
marcjulian profile image
Marc Stammerjohann

I am not sure if I understand your question.

You use Graphql code first approach to generate your graphql api which generates the schema.gql.

The schema.prisma describes your database and is separate to the graphql endpoint. You have to write your prisma schema your self.

That means you can use both at the same time. Code first to generate schema.gql and write the database schema.prisma manually.

Does that answer your question?

Collapse
 
mark600211 profile image
mark600211

thanks for answer. I get it yesterday, when start do it, not read) But this good idea, I think: we have slightly different between our schema.gql and schema.prisma. Cause we would be generate prisma use our ObjectType objects. it would be pretty nice.

Collapse
 
mark600211 profile image
mark600211 • Edited

Hi Marc,

Great post. I saw your repository and found out that you use Prisma Client there. But in official docs of nest they tell about Prisma bindings. What's the different?

Collapse
 
marcjulian profile image
Marc Stammerjohann • Edited

Hi Mark, nice name by the way πŸ˜„!

Yes the nest docs describe how to integrate with prisma bindings which are used for Prisma 1. I am using the Prisma client from Prisma 2.

Here you can read a bit more about Prisma history prisma.io/blog/prisma-and-graphql-... which explains the terms.