1. Cài đặt GraphQL
npm install @nestjs/graphql @nestjs/apollo @apollo/server graphql
// app.module.ts
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
sortSchema: true,
playground: process.env.NODE_ENV !== 'production',
context: ({ req, res }) => ({ req, res }),
}),
],
})
export class AppModule {}
2. Object Types (Code-first)
// users/models/user.model.ts
import { ObjectType, Field, ID, Int } from '@nestjs/graphql';
@ObjectType()
export class User {
@Field(() => ID)
id: string;
@Field()
email: string;
@Field()
name: string;
@Field({ nullable: true })
avatar?: string;
@Field(() => Int)
age: number;
@Field(() => [Post], { nullable: 'items' })
posts?: Post[];
@Field()
createdAt: Date;
}
@ObjectType()
export class Post {
@Field(() => ID)
id: string;
@Field()
title: string;
@Field()
content: string;
@Field(() => User)
author: User;
}
3. Resolvers
// users/users.resolver.ts
import { Resolver, Query, Mutation, Args, ResolveField, Parent } from '@nestjs/graphql';
@Resolver(() => User)
export class UsersResolver {
constructor(
private usersService: UsersService,
private postsService: PostsService,
) {}
@Query(() => [User], { name: 'users' })
findAll(
@Args('limit', { type: () => Int, defaultValue: 10 }) limit: number,
@Args('offset', { type: () => Int, defaultValue: 0 }) offset: number,
) {
return this.usersService.findAll({ limit, offset });
}
@Query(() => User, { name: 'user' })
findOne(@Args('id', { type: () => ID }) id: string) {
return this.usersService.findById(id);
}
// ResolveField cho nested relation
@ResolveField(() => [Post])
posts(@Parent() user: User) {
return this.postsService.findByAuthorId(user.id);
}
}
4. Input Types và Mutations
// users/dto/create-user.input.ts
import { InputType, Field } from '@nestjs/graphql';
import { IsEmail, MinLength } from 'class-validator';
@InputType()
export class CreateUserInput {
@Field()
@IsEmail()
email: string;
@Field()
@MinLength(2)
name: string;
@Field()
@MinLength(8)
password: string;
}
@InputType()
export class UpdateUserInput extends PartialType(CreateUserInput) {
@Field(() => ID)
id: string;
}
// Mutations trong resolver
@Mutation(() => User)
createUser(@Args('input') input: CreateUserInput) {
return this.usersService.create(input);
}
@Mutation(() => User)
@UseGuards(GqlAuthGuard)
updateUser(
@Args('input') input: UpdateUserInput,
@CurrentUser() currentUser: User,
) {
return this.usersService.update(input.id, input);
}
@Mutation(() => Boolean)
@UseGuards(GqlAuthGuard)
deleteUser(@Args('id', { type: () => ID }) id: string) {
return this.usersService.delete(id);
}
5. Authentication cho GraphQL
// auth/gql-auth.guard.ts
import { ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
// Current user decorator cho GraphQL
export const CurrentUser = createParamDecorator(
(data: unknown, context: ExecutionContext) => {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req.user;
},
);
6. DataLoader — Giải quyết N+1
npm install dataloader
// common/dataloader/users.loader.ts
import DataLoader from 'dataloader';
@Injectable({ scope: Scope.REQUEST })
export class UsersLoader {
constructor(private usersService: UsersService) {}
readonly batchUsers = new DataLoader<string, User>(async (ids: string[]) => {
const users = await this.usersService.findByIds([...ids]);
const usersMap = new Map(users.map((u) => [u.id, u]));
return ids.map((id) => usersMap.get(id));
});
}
// Sử dụng trong resolver
@ResolveField(() => User)
author(@Parent() post: Post) {
return this.usersLoader.batchUsers.load(post.authorId);
}
7. Subscriptions (Real-time)
// app.module.ts - Enable subscriptions
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
subscriptions: {
'graphql-ws': true,
},
})
// posts/posts.resolver.ts
import { PubSub } from 'graphql-subscriptions';
const pubSub = new PubSub();
@Resolver(() => Post)
export class PostsResolver {
@Mutation(() => Post)
async createPost(@Args('input') input: CreatePostInput) {
const post = await this.postsService.create(input);
pubSub.publish('postCreated', { postCreated: post });
return post;
}
@Subscription(() => Post)
postCreated() {
return pubSub.asyncIterableIterator('postCreated');
}
}
8. Tổng kết
- Code-first: Dùng decorators để generate schema tự động
- Resolvers: Query, Mutation, Subscription, ResolveField
- DataLoader: Batch queries, giải quyết N+1 problem
- Auth: GqlAuthGuard + CurrentUser decorator
- Subscriptions: Real-time updates qua WebSocket
Bài tiếp theo sẽ tìm hiểu File Upload, Caching và Task Scheduling.