Building graphql apis using nestjs graphql || Code First Approach 🍾
I previous Blog we have discussed about schema first user graphql service, In this Blog we will talk about code first approach using typescript annotations
http://tkssharma.com/nestjs-with-graphql-using-nestjs-graphql
In Code-first development rather than creating the schema first, we first start creating the resolvers. So by the resolvers, the schema will be automatically generated. So here we will not have to create the schema manually. But the disadvantage will be that it would be difficult to understand the schema by just looking at the code.
In a code first approach, the schema is programatically generated based on the types used in your code, rather than using the SDL. Code first approaches work particularly well when using a typed language like TypeScript as certain field types can be inferred. Here’s an example of a basic type definition using code first:
@ObjectType()
export default class User {
@Field()
id: string;
@Field()
email: string;
@Field()
name: string;
@Field({ nullable: true })
facebookId: string;
@Field({ nullable: true })
googleId: string;
@Field({ defaultValue: false })
verified: boolean;
@Field()
fullName: string;
}
Typescript is a language build on Javascript where we can use static type definitions. By using static type checking we can ideally remove many errors during the compile time rather than identifying them as errors in runtime which is really helpful in large level of applications. Apart from static type checking, many more developer features like IntelliSense are also will be available by using Typescript. Because of these features, many large-level Javascript projects opt to use Typescript on top of their Javascript projects to enhance its uses. This goes true for GraphQL APIs as well.
Graphql Main Module
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config'
import * as Joi from 'joi';
import { TypeOrmModule, TypeOrmModuleAsyncOptions } from '@nestjs/typeorm';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { ProjectModule } from './project/project.module';
import { CategoryModule } from './category/category.module';
import { EmployeeModule } from './employee/employee.module';
@Module({
imports: [
ConfigModule.forRoot({
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'local')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required()
})
}),
// same as Part-1
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
return {
name: 'default',
type: 'postgres',
logging: true,
url: configService.get('DATABASE_URL'),
entities: [__dirname + '/**/**.entity{.ts,.js}'],
synchronize: true
} as TypeOrmModuleAsyncOptions;
}
}),
GraphQLModule.forRoot({
definitions: {
path: join(process.cwd(), 'src/graphql.schama.ts'),
outputAs: 'class'
}
}),
ProjectModule,
EmployeeModule,
CategoryModule,
]
})
export class DomainModule {
}
Lets talk about project Module
- dto
- entities
- project.module.ts
- project.resolver.ts
- project.service.ts
Project Module
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Project } from "./entities/project.entity";
import { ProjectResolver } from "./project.resolver";
import { ProjectService } from "./project.service";
@Module({
imports: [
TypeOrmModule.forFeature([Project])
],
providers: [
ProjectResolver, ProjectService
]
})
export class ProjectModule {
}
Project Resolver
import { Args, Int, Mutation, Query, Resolver } from "@nestjs/graphql";
import { CreateProjectInput } from "./dto/create-project.input";
import { UpdateProjectInput } from "./dto/update-project.input";
import { Project } from "./entities/project.entity";
import { ProjectService } from './project.service';
@Resolver(() => Project)
export class ProjectResolver {
constructor(
private readonly projectService: ProjectService
) { }
@Mutation(() => Project, { name: 'createProject' })
createProject(@Args('createProjectInput') createProjectInput: CreateProjectInput) {
return this.projectService.create(createProjectInput);
}
// update
@Mutation(() => Project, { name: 'updateProject' })
updateProject(@Args('updateProjectInput') updateProjectInput: UpdateProjectInput) {
return this.projectService.update(updateProjectInput.id, updateProjectInput);
}
@Query(() => Project, { name: 'findOneProject' })
findOne(@Args('id', { type: () => Int }) id: number) {
return this.projectService.findOne(id);
}
@Query(() => [Project], { name: 'findAllProjects' })
findAll() {
return this.projectService.findAll();
}
@Mutation(() => Project, { name: 'removeProject' })
removeProject(@Args('id', { type: () => Int }) id: number) {
return this.projectService.delete(id);
}
}
Project Service
import { InjectRepository } from "@nestjs/typeorm";
import { Injectable } from '@nestjs/common';
import { Project } from "./entities/project.entity";
import { CreateProjectInput } from './dto/create-project.input';
import { UpdateProjectInput } from "./dto/update-project.input";
import { Repository } from "typeorm";
@Injectable()
export class ProjectService {
constructor(@InjectRepository(Project) private projectRepository: Repository<Project>) {
}
async create(input: CreateProjectInput) {
let project = this.projectRepository.create(input);
return await this.projectRepository.save(project);
}
async findAll(): Promise<Project[]> {
// lazy fetch
return await this.projectRepository.find({
relations: ["employees", "employees.categories"]
})
}
async findOne(id: number): Promise<Project> {
// lazy fetch
return await this.projectRepository.findOne({
where: { id },
relations: ["employees"]
})
}
async update(id: number, input: UpdateProjectInput): Promise<Project> {
// lazy fetch
const project = await this.projectRepository.findOne({ where: { id } });
if (project) {
project.code = input.code;
project.name = input.name;
return await this.projectRepository.save(project);
}
}
async delete(id: number): Promise<any> {
return await this.projectRepository.delete(id)
}
}
major updates here is.. here instead of using schema we are using dto classes with @InputType and ObjectType and with @Field annotations
import { InputType, Int, Field } from '@nestjs/graphql';
@InputType()
export class CreateProjectInput {
@Field()
name: string;
@Field(() => Int)
code: number;
}
In both approaches we will see major changes like
- in code first we will see use of DTO's
- we will see Graphql Schema defined in schema first approach
Now we can start application and see the magic on that PORT
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
References
- https://www.apollographql.com/docs/federation/
- https://www.apollographql.com/docs/federation/federation-2/new-in-federation-2
- https://github.com/apollographql/supergraph-demo-fed2
- https://www.apollographql.com/docs/federation/federation-spec/
- https://docs.nestjs.com/graphql/migration-guide
- https://docs.nestjs.com/graphql/federation
Comments