NestJS và GraphQL năm 2026: Schema, Resolver và Câu hỏi Phỏng vấn
Hướng dẫn toàn diện về tích hợp NestJS GraphQL với phương pháp schema-first và code-first, các pattern resolver, cùng câu hỏi phỏng vấn cho vị trí senior developer.

Tích hợp NestJS GraphQL cung cấp phương pháp có cấu trúc để xây dựng API type-safe bằng cách tận dụng decorator TypeScript và ngôn ngữ truy vấn GraphQL. Bài hướng dẫn này bao gồm cả phương pháp schema-first và code-first, các pattern resolver, cùng những câu hỏi phỏng vấn mà hiring manager thường hỏi trong năm 2026.
NestJS 11 mặc định sử dụng GraphQL code-first, sinh schema từ các class TypeScript. Schema-first vẫn khả dụng cho các team đã có sẵn file .graphql hoặc workflow dựa trên SDL.
Cấu hình NestJS GraphQL với Apollo Server
NestJS tích hợp với Apollo Server thông qua package @nestjs/graphql. Cấu hình khác nhau tùy thuộc vào phương pháp được chọn—code-first sinh SDL từ decorator, trong khi schema-first parse trực tiếp các file .graphql.
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/schema.gql'), // Code-first: generates schema
sortSchema: true, // Alphabetical ordering for readability
playground: process.env.NODE_ENV !== 'production', // Disable in prod
introspection: process.env.NODE_ENV !== 'production',
}),
],
})
export class AppModule {}Tùy chọn autoSchemaFile kích hoạt chế độ code-first. Đặt thành true sẽ sinh schema trong bộ nhớ mà không ghi ra disk—hữu ích cho các deployment serverless nơi quyền truy cập filesystem có thể bị hạn chế.
Định nghĩa GraphQL Type với Decorator Code-First
Code-first định nghĩa các GraphQL type bằng class TypeScript được trang trí với @ObjectType(). Mỗi field sử dụng @Field() để chỉ định GraphQL type và tính nullability.
import { ObjectType, Field, ID, Int } from '@nestjs/graphql';
@ObjectType({ description: 'Application user' }) // Description appears in schema docs
export class User {
@Field(() => ID) // Maps to GraphQL ID scalar
id: string;
@Field()
email: string;
@Field({ nullable: true }) // Optional field in GraphQL
displayName?: string;
@Field(() => Int, { defaultValue: 0 })
postCount: number;
@Field(() => [Post], { nullable: 'itemsAndList' }) // Both list and items can be null
posts?: Post[];
// Fields without @Field() are excluded from GraphQL schema
passwordHash: string;
}Tùy chọn nullable chấp nhận ba giá trị: true (field là tùy chọn), 'items' (các item trong list có thể null), và 'itemsAndList' (cả list và item đều có thể null). Độ chi tiết này phù hợp chính xác với ngữ nghĩa nullability của GraphQL.
Xây dựng Resolver cho Query và Mutation
Resolver xử lý các thao tác GraphQL đến. NestJS sử dụng @Resolver() để đánh dấu một class là resolver, với các method decorator chỉ định loại thao tác.
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { User } from './user.entity';
import { UsersService } from './users.service';
import { CreateUserInput } from './dto/create-user.input';
@Resolver(() => User) // Binds resolver to User type for field resolution
export class UsersResolver {
constructor(private readonly usersService: UsersService) {}
@Query(() => [User], { name: 'users' }) // Explicit query name
findAll(): Promise<User[]> {
return this.usersService.findAll();
}
@Query(() => User, { nullable: true })
user(@Args('id', { type: () => ID }) id: string): Promise<User | null> {
return this.usersService.findOne(id);
}
@Mutation(() => User)
createUser(@Args('input') input: CreateUserInput): Promise<User> {
return this.usersService.create(input);
}
}Decorator @Resolver(() => User) thiết lập ngữ cảnh cho các decorator @ResolveField(), cho phép phân giải ở cấp field cho dữ liệu được tính toán hoặc liên quan.
Input Type và Validation với class-validator
GraphQL input type định nghĩa payload cho mutation. Kết hợp @InputType() với các decorator class-validator cho phép validation ở cả cấp schema và runtime.
import { InputType, Field } from '@nestjs/graphql';
import { IsEmail, MinLength, IsOptional, Matches } from 'class-validator';
@InputType()
export class CreateUserInput {
@Field()
@IsEmail({}, { message: 'Invalid email format' })
email: string;
@Field()
@MinLength(8, { message: 'Password must be at least 8 characters' })
@Matches(/[A-Z]/, { message: 'Password must contain uppercase letter' })
password: string;
@Field({ nullable: true })
@IsOptional()
@MinLength(2)
displayName?: string;
}Kích hoạt validation toàn cục bằng cách thêm ValidationPipe trong main.ts. Các lỗi GraphQL bao gồm thông báo validation trong field extensions, duy trì khả năng tương thích với client.
Sẵn sàng chinh phục phỏng vấn Node.js / NestJS?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Phân giải Dữ liệu Liên quan với @ResolveField và DataLoader
Field resolver xử lý các mối quan hệ giữa các type. Nếu không có tối ưu hóa, việc lấy danh sách user cùng với các bài post sẽ kích hoạt N+1 query—một cho user, sau đó một cho mỗi user để lấy post.
import { Resolver, ResolveField, Parent } from '@nestjs/graphql';
import { User } from './user.entity';
import { Post } from '../posts/post.entity';
import { PostsLoader } from '../posts/posts.loader';
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly postsLoader: PostsLoader) {}
@ResolveField(() => [Post])
async posts(@Parent() user: User): Promise<Post[]> {
// DataLoader batches requests: one query for all user IDs
return this.postsLoader.batchByUserId.load(user.id);
}
@ResolveField(() => Int)
async postCount(@Parent() user: User): Promise<number> {
const posts = await this.postsLoader.batchByUserId.load(user.id);
return posts.length;
}
}DataLoader nhóm và cache các request trong một thao tác GraphQL. Đối với NestJS, đặt scope của DataLoader ở cấp request bằng cách sử dụng @Injectable({ scope: Scope.REQUEST }).
import { Injectable, Scope } from '@nestjs/common';
import * as DataLoader from 'dataloader';
import { PostsService } from './posts.service';
import { Post } from './post.entity';
@Injectable({ scope: Scope.REQUEST }) // New instance per request
export class PostsLoader {
constructor(private readonly postsService: PostsService) {}
public readonly batchByUserId = new DataLoader<string, Post[]>(
async (userIds: readonly string[]) => {
// Single query: SELECT * FROM posts WHERE user_id IN (...)
const posts = await this.postsService.findByUserIds([...userIds]);
// Map results back to input order
const postsMap = new Map<string, Post[]>();
posts.forEach(post => {
const existing = postsMap.get(post.userId) || [];
postsMap.set(post.userId, [...existing, post]);
});
return userIds.map(id => postsMap.get(id) || []);
}
);
}DataLoader giảm N+1 query xuống còn một query batch duy nhất, rất quan trọng cho các API GraphQL nơi client kiểm soát độ sâu của query. Để tìm hiểu sâu hơn về các pattern kiến trúc NestJS, xem câu hỏi phỏng vấn NestJS Modules & Dependency Injection.
Subscription cho Dữ liệu Real-Time
GraphQL subscription đẩy dữ liệu đến client qua kết nối WebSocket. NestJS sử dụng thư viện graphql-ws, triển khai giao thức GraphQL over WebSocket.
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
subscriptions: {
'graphql-ws': true, // Modern protocol
'subscriptions-transport-ws': false, // Deprecated legacy protocol
},
}),import { Resolver, Subscription } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { Post } from './post.entity';
const pubSub = new PubSub(); // Use Redis PubSub for multi-instance deployments
@Resolver(() => Post)
export class PostsResolver {
@Subscription(() => Post, {
filter: (payload, variables) =>
payload.postCreated.userId === variables.userId, // Client-side filtering
})
postCreated() {
return pubSub.asyncIterableIterator('postCreated');
}
@Mutation(() => Post)
async createPost(@Args('input') input: CreatePostInput): Promise<Post> {
const post = await this.postsService.create(input);
pubSub.publish('postCreated', { postCreated: post }); // Trigger subscription
return post;
}
}Đối với các deployment production trên nhiều instance, thay thế PubSub trong bộ nhớ bằng graphql-redis-subscriptions để broadcast event trên toàn cluster.
Xác thực và Phân quyền trong GraphQL
NestJS Guard hoạt động liền mạch với GraphQL resolver. Execution context khác với REST—sử dụng GqlExecutionContext để trích xuất request.
import { Injectable, ExecutionContext } 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; // Extract request from GraphQL context
}
}Áp dụng guard ở cấp resolver hoặc method. Phân quyền cấp field sử dụng @ResolveField() với logic điều kiện dựa trên role của user.
@UseGuards(GqlAuthGuard)
@Resolver(() => User)
export class UsersResolver {
@Query(() => User)
me(@CurrentUser() user: User): User {
return user; // Return authenticated user
}
@ResolveField(() => String, { nullable: true })
email(@Parent() user: User, @CurrentUser() currentUser: User): string | null {
// Only return email if viewing own profile or admin
if (user.id === currentUser.id || currentUser.role === 'ADMIN') {
return user.email;
}
return null;
}
}Đối với phân quyền phức tạp, cân nhắc sử dụng GraphQL Shield hoặc tích hợp CASL có sẵn của NestJS. Bài viết NestJS Guards and Interceptors thảo luận chi tiết về các pattern này.
Câu hỏi Phỏng vấn NestJS GraphQL Phổ biến
Phỏng vấn kỹ thuật cho các vị trí NestJS thường bao gồm các câu hỏi cụ thể về GraphQL. Dưới đây là các pattern mà hiring manager đánh giá.
H: NestJS xử lý vấn đề N+1 trong GraphQL như thế nào?
DataLoader nhóm các lời gọi field resolver trong một request. Khi nhiều đối tượng parent yêu cầu cùng một field, DataLoader thu thập tất cả các key, thực thi một query batch, và phân phối kết quả. Loader phải có scope request để tránh các vấn đề caching giữa các request.
H: Sự khác biệt giữa code-first và schema-first trong NestJS GraphQL là gì?
Code-first sinh GraphQL schema từ các decorator TypeScript lúc runtime, giữ cho type và schema tự động đồng bộ. Schema-first parse các file SDL .graphql, yêu cầu định nghĩa type thủ công. Code-first phù hợp với các team native TypeScript; schema-first hoạt động tốt hơn khi schema là contract giữa các team frontend và backend.
H: Làm thế nào để triển khai permission ở cấp field?
Có ba cách tiếp cận: (1) @ResolveField() với return có điều kiện dựa trên context user, (2) Custom decorator kiểm tra permission trước khi phân giải field, (3) Schema directive như @auth(requires: ADMIN) được xử lý bởi directive transformer. Cách tiếp cận đầu tiên cung cấp tính linh hoạt cao nhất; directive cung cấp documentation schema rõ ràng nhất.
H: Giải thích GraphQL context trong NestJS.
Đối tượng context được truyền qua tất cả các resolver trong một request. NestJS mặc định điền HTTP request vào đó. Context tùy chỉnh được cấu hình trong GraphQLModule.forRoot() thông qua tùy chọn context—hữu ích để thêm các instance DataLoader, user đã xác thực, hoặc kết nối database.
H: Subscription được scale như thế nào trên nhiều instance server?
PubSub trong bộ nhớ chỉ hoạt động cho deployment single-instance. Kiến trúc multi-instance yêu cầu broker bên ngoài—Redis PubSub là tiêu chuẩn. Mỗi instance server subscribe vào các channel Redis; khi bất kỳ instance nào publish event, tất cả các instance đều nhận được và đẩy đến các client WebSocket đang kết nối.
Để chuẩn bị phỏng vấn NestJS thêm, khám phá module Middleware và Interceptors bao gồm các pattern request lifecycle.
Kết luận
- NestJS GraphQL hỗ trợ cả phương pháp code-first và schema-first—code-first đơn giản hóa các dự án TypeScript, schema-first phù hợp với workflow dựa trên SDL
- DataLoader loại bỏ N+1 query bằng cách nhóm các request field resolver trong một thao tác
- Các instance DataLoader có scope request ngăn chặn ô nhiễm cache giữa các request đồng thời
GqlExecutionContextkết nối NestJS Guard với context resolver GraphQL- Subscription production yêu cầu Redis PubSub để phân phối event multi-instance
- Phân quyền cấp field kết hợp
@ResolveField()với kiểm tra context user
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

Microservices với NestJS năm 2026: Kiến trúc, gRPC và Câu hỏi Phỏng vấn
Hướng dẫn toàn diện về kiến trúc microservices NestJS với gRPC: transport layer, Protocol Buffers, streaming patterns và câu hỏi phỏng vấn cho backend engineer năm 2026.

NestJS + Prisma: stack backend hiện đại cho Node.js
Hướng dẫn đầy đủ để xây dựng API backend hiện đại với NestJS và Prisma. Cấu hình, model, service, transaction và các best practice được giải thích chi tiết.

NestJS: Xây dựng REST API hoàn chỉnh từ đầu
Hướng dẫn đầy đủ xây dựng REST API chuyên nghiệp với NestJS. Controller, Service, Module, xác thực dữ liệu với class-validator và xử lý lỗi được giải thích chi tiết.