GraphQL Code-First Approach with NestJS 7
Create a GraphQL API using Code-First Approach with NestJS 7.
- Authors
- Marc Stammerjohann
- Published at
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 schemaname
: property name in the schema (createdAt
=>registeredAt
)description
: adding a field descriptiondeprecationReason
: adding a deprecation noticenullable
: 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.
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.
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.
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;
}
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.
Sponsor us
Did you find this post useful? We at notiz.dev write about our experiences developing Apps, Websites and APIs and develop Open Source tools. Your support would mean a lot to us 🙏. Receive a reward by sponsoring us on Patreon or start with a one-time donation on GitHub Sponsors.