NestJS Resolvers FT GraphQL Code-First and Schema-First ๐
Posted October 7, 2023
GraphQL uses Resolvers to determine how to retrieve or manipulate the data associated with each field in a schema. Resolvers connects a GraphQL schema with the underlying data sources.
Take an example of a Rest API. You use controllers to create HTTP Methods. Now, GraphQL canโt use the same structure. Here, the Resolvers transform the raw data into the shape expected by the GraphQL schema. This way, your Resolvers are your controllers for GraphQL in ensuring you create methods for queries, subscriptions, and mutations.
To make Resolvers dynamic, you can choose between Code-First and Schema-First to expand the GraphQL ecosystem.
This guide dives deeper into GraphQL Resolvers and how to use Code-First and Schema-First to model your schema featuring NestJS.
Resolvers with Code-First GraphQL schema
This approach doesnโt allow you to write GraphQL SDL manually. You must use Decorators; the schema (SDL) will be generated automatically.
Related: NestJS GraphQL API with TypeORM, Apollo Server and PostgreSQL
Think of this approach as a Typical database ORM where you use decorators to define how database table attributes are represented. You only use the Code approach to build your tables, attributes, and Relationships here. Then, the ORM automatically generates migrations and tables for you.
The same narrative applies to the Code-First GraphQL schema. Resolvers and types are discovered and processed automatically by the framework you are using.
Code-First Example NestJS GraphQL Resolver
Letโs look at the basics of Code First implementation. Code-First uses TypeScript decorators @ObjectType()
, @Field()
From GraphQL and NestJS. Now, you will go ahead and create GraphQL types and schema directly in TypeScript classes as follows:
import { ObjectType, Field } from '@nestjs/graphql';
@ObjectType()
export class Task {
@Field()
id: string;
@Field()
title: string;
@Field()
completed: boolean;
}
Now you will go ahead and create your Resolvers:
import { Query, Resolver, Args, Mutation } from '@nestjs/graphql';
import { Task } from './task.entity';
import { TaskService } from './task.service';
@Resolver((of) => Task)
export class TaskResolver {
constructor(private readonly taskService: TaskService) {}
@Query((returns) => [Task])
tasks(): Task[] {
return this.taskService.findAll();
}
// Other resolvers
}
You then tell the NestJS entry point to use GraphQLModule
and autoSchemaFile
. At runtime, NestJS will generate your schema. This means the Schema is being generated based on how you create your types and Resolvers.
Resolvers with Schema-First GraphQL Schema
Code-First doesnโt allow GraphQL to autogenerate the schema. The GraphQL schema is defined using the GraphQL Schema Definition Language (SDL) in a separate file. Here, you implement Resolvers to match the schema structure you defined in the SDL.
Again, take the ORM approach. With it, you can explicitly create your tables and related attributes. And Schema-First isnโt different. Resolvers explicitly connect with the schema, specifying how each field should be resolved.
Example Resolver
Go directly and create your schema.gql
and add your schema. For example:
type Task {
id: ID!
title: String!
completed: Boolean!
}
type Query {
tasks: [Task]
task(id: ID!): Task
}
type Mutation {
createTask(title: String!): Task
updateTask(id: ID!, title: String!): Task
deleteTask(id: ID!): Task
}
This is the exact Schema you want your application to follow. Likewise, the resolvers you create must adhere to it without any compromise.
How to Create NestJS Resolvers FT GraphQL Code-First
Letโs dive into the actual implementation of Resolvers FT GraphQL Code-First. You will use NestJS.
Ensure you have NestJS CLI ready and create your app:
npm i -g @nestjs/cli
nest new nestjs-graphql
This should create a NestJS application that you cd
to:
cd nestjs-graphql
You first need to install GraphQL and its related packages:
npm install graphql @nestjs/graphql @nestjs/apollo apollo-server-express
To create any GraphQL-related app with NestJS, use the following command as a resource:
nest g resource task --no-spec
This command scaffolds the fundamental building blocks of GraphQL. So, make sure you select your Schema Modelling. Here, you are working with the Code First approach as follows:
Also, Would you like to generate CRUD entry points? make it yes:
Look at your file:
You donโt have a schema.gql
file. It is generated during runtime. However, you have an entities/task.entity.ts
file. Itโs here you define the types of your schema using @ObjectType()
and @Field()
from GraphQL decorators as follows:
import { ObjectType, Field } from '@nestjs/graphql';
@ObjectType()
export class Task {
@Field()
id: string;
@Field()
title: string;
@Field()
completed: boolean;
}
Still, on your code, you will create your resolvers in the task/task.resolver.ts
file. This includes all mutations, queries, and subscriptions you need to have in your GraphQL API.
However, you must have the right Providers using the NestJS Service file to abstract the login of your resolvers. In task/task.service.ts
add Providers using @Injectable
decorator:
// src/task/task.service.ts
import { Injectable } from '@nestjs/common';
import { Task } from './entities/task.entity';
@Injectable()
export class TaskService {
private tasks: Task[] = [];
create(task: Task): Task {
task.id = (this.tasks.length + 1).toString();
this.tasks.push(task);
return task;
}
findAll(): Task[] {
return this.tasks;
}
}
Based on this Provider, you will create a Mutation and Query resolvers in task/task.resolver.ts
as follows:
// src/task/task.resolver.ts
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { Task } from './entities/task.entity';
import { TaskService } from './task.service';
@Resolver((of) => Task)
export class TaskResolver {
constructor(private readonly taskService: TaskService) {}
@Query((returns) => [Task])
tasks(): Task[] {
return this.taskService.findAll();
}
@Mutation((returns) => Task)
createTask(@Args('title') title: string): Task {
const newTask: Task = { id: '', title, completed: false };
return this.taskService.create(newTask);
}
}
Still, your application entry point must know when and where to generate your SDL Based on the resolvers and type you have created. In your app.module.ts
include the GraphQLModule
to load the resolvers using Apollo drivers:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { TaskModule } from './task/task.module';
import { join } from 'path';
import { ApolloDriverConfig, ApolloDriver } from '@nestjs/apollo';
@Module({
imports: [
TaskModule,
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
],
})
export class AppModule {}
Go run your app:
npm run start:dev
Based on this Code First Schema generation, a src/schema.gql
will be created at runtime with autogenerated SDL for your app:
Have you understood how Code First Schema works with your Resolver? I have created this Comprehensive NestJS GraphQL API with TypeORM, Apollo Server, and Postgres Guide. It perfectly extends this topic to creating a full-fledged GraphQL API to include your database.
Creating NestJS Resolvers FT GraphQL Schema First
To use this approach, scaffold the fundamental building blocks of GraphQL using your command as follows:
nest g resource post --no-spec
Make sure you select your Schema Modelling. Here, you are working with the schema-first approach as follows:
Also, Would you like to generate CRUD entry points? make it yes:
Look at your file structure:
You will have a post.graphql
file to create your GraphQL Schema First SDL.
This approach looks like reverse engineering what you created using Code First. You explicitly create how each field should be resolved.
You Create a Schema to dictate the structure of your types and resolvers. Update the post.graphql
file.
Using NestJS Resolvers with Schema-First approach
If you want to use this method, NestJS has a great ready code you can refer to. Itโs a long process that will extend this post to another guide.
I will leave this NestJS GitHub Repo as a perfect reference to GraphQL Schema-First Apollo sample.
Choosing Between Code-First and Schema-First NestJS Resolvers
Code-First is perfectly great if you need:
- Direct integration with TypeScript and type checking.
- Less boilerplate codes.
- When you prefer a more code-centric approach.
- Your GraphQL schema is tightly integrated with your TypeScript types.
When and why should you choose to use Schema-First Resolvers? Here are my opinions:
- If you want a clear separation of concerns between schema and implementation.
- When you have a well-defined schema and want to keep it separate from the implementation.
- You have teams working with separate roles for frontend and backend development.
Conclusion
GraphQL ecosystem is diverse and dynamic. And Iโm sure you chose it due to its advantages over REST APIs. I hope you learned something when working on your GraphQL NestJS resolvers.