NestJS และ GraphQL ในปี 2026: Schema, Resolver และคำถามสัมภาษณ์
คู่มือครบถ้วนสำหรับการผสานรวม NestJS GraphQL ด้วยแนวทาง schema-first และ code-first, รูปแบบ resolver และคำถามสัมภาษณ์สำหรับตำแหน่ง senior developer

การผสานรวม NestJS GraphQL มอบแนวทางที่มีโครงสร้างในการสร้าง API ที่ type-safe โดยใช้ประโยชน์จาก decorator ของ TypeScript และภาษา query ของ GraphQL บทความนี้ครอบคลุมทั้งแนวทาง schema-first และ code-first, รูปแบบ resolver รวมถึงคำถามสัมภาษณ์ที่ hiring manager มักถามในปี 2026
NestJS 11 ใช้ GraphQL แบบ code-first เป็นค่าเริ่มต้น โดยสร้าง schema จาก TypeScript class แนวทาง schema-first ยังคงมีให้ใช้สำหรับทีมที่มีไฟล์ .graphql อยู่แล้วหรือ workflow ที่ใช้ SDL
การตั้งค่า NestJS GraphQL กับ Apollo Server
NestJS ผสานรวมกับ Apollo Server ผ่าน package @nestjs/graphql การตั้งค่าจะแตกต่างกันตามแนวทางที่เลือก—code-first สร้าง SDL จาก decorator ในขณะที่ schema-first parse ไฟล์ .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 {}ตัวเลือก autoSchemaFile เปิดใช้งานโหมด code-first หากตั้งค่าเป็น true จะสร้าง schema ในหน่วยความจำโดยไม่เขียนลง disk—มีประโยชน์สำหรับการ deploy แบบ serverless ที่การเข้าถึง filesystem อาจถูกจำกัด
การกำหนด GraphQL Type ด้วย Decorator แบบ Code-First
Code-first กำหนด GraphQL type โดยใช้ TypeScript class ที่ตกแต่งด้วย @ObjectType() แต่ละ field ใช้ @Field() เพื่อระบุ GraphQL type และ 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;
}ตัวเลือก nullable รับค่าสามค่า: true (field เป็น optional), 'items' (item ใน list สามารถเป็น null), และ 'itemsAndList' (ทั้ง list และ item สามารถเป็น null) ความละเอียดนี้ตรงกับ semantic ของ nullability ใน GraphQL อย่างแม่นยำ
การสร้าง Resolver สำหรับ Query และ Mutation
Resolver จัดการ operation ของ GraphQL ที่เข้ามา NestJS ใช้ @Resolver() เพื่อทำเครื่องหมาย class เป็น resolver โดยมี method decorator ระบุประเภทของ operation
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) กำหนดบริบทสำหรับ decorator @ResolveField() ทำให้สามารถ resolve ในระดับ field สำหรับข้อมูลที่คำนวณหรือเกี่ยวข้อง
Input Type และ Validation ด้วย class-validator
GraphQL input type กำหนด payload สำหรับ mutation การรวม @InputType() กับ decorator ของ class-validator ช่วยให้สามารถ validate ทั้งในระดับ schema และ 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;
}เปิดใช้งาน validation ทั่วทั้งระบบโดยเพิ่ม ValidationPipe ใน main.ts error ของ GraphQL จะรวมข้อความ validation ใน field extensions เพื่อรักษาความเข้ากันได้กับ client
พร้อมที่จะพิชิตการสัมภาษณ์ Node.js / NestJS แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การ Resolve ข้อมูลที่เกี่ยวข้องด้วย @ResolveField และ DataLoader
Field resolver จัดการความสัมพันธ์ระหว่าง type หากไม่มีการเพิ่มประสิทธิภาพ การดึงรายชื่อ user พร้อมกับ post จะทำให้เกิด N+1 query—หนึ่งสำหรับ user แล้วอีกหนึ่งต่อ user สำหรับ 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 รวมกลุ่มและ cache request ภายใน operation GraphQL เดียว สำหรับ NestJS ให้กำหนด scope ของ DataLoader เป็นระดับ request โดยใช้ @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 ลด N+1 query ให้เหลือ query batch เดียว ซึ่งสำคัญมากสำหรับ API GraphQL ที่ client ควบคุมความลึกของ query สำหรับการศึกษาเพิ่มเติมเกี่ยวกับรูปแบบสถาปัตยกรรม NestJS ดู คำถามสัมภาษณ์ NestJS Modules & Dependency Injection
Subscription สำหรับข้อมูล Real-Time
GraphQL subscription ส่งข้อมูลไปยัง client ผ่านการเชื่อมต่อ WebSocket NestJS ใช้ library graphql-ws ซึ่ง implement โปรโตคอล 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;
}
}สำหรับการ deploy แบบ production บนหลาย instance ให้แทนที่ PubSub ในหน่วยความจำด้วย graphql-redis-subscriptions เพื่อกระจาย event ไปทั่วทั้ง cluster
Authentication และ Authorization ใน GraphQL
NestJS Guard ทำงานร่วมกับ GraphQL resolver ได้อย่างราบรื่น execution context แตกต่างจาก REST—ใช้ GqlExecutionContext เพื่อแยก 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
}
}ใช้ guard ในระดับ resolver หรือ method การ authorize ในระดับ field ใช้ @ResolveField() กับ logic เงื่อนไขตาม role ของ 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;
}
}สำหรับการ authorize ที่ซับซ้อน พิจารณาใช้ GraphQL Shield หรือการผสานรวม CASL ที่มีอยู่ใน NestJS บทความ NestJS Guards and Interceptors อธิบายรูปแบบเหล่านี้อย่างละเอียด
คำถามสัมภาษณ์ NestJS GraphQL ที่พบบ่อย
การสัมภาษณ์เทคนิคสำหรับตำแหน่ง NestJS มักรวมคำถามเฉพาะเกี่ยวกับ GraphQL ต่อไปนี้คือรูปแบบที่ hiring manager ประเมิน
ถ: NestJS จัดการปัญหา N+1 ใน GraphQL อย่างไร?
DataLoader รวมกลุ่มการเรียก field resolver ภายใน request เดียว เมื่อหลาย parent object ร้องขอ field เดียวกัน DataLoader จะรวบรวม key ทั้งหมด execute query batch เดียว และแจกจ่ายผลลัพธ์ loader ต้องมี scope เป็น request เพื่อป้องกันปัญหา caching ข้าม request
ถ: ความแตกต่างระหว่าง code-first และ schema-first ใน NestJS GraphQL คืออะไร?
Code-first สร้าง GraphQL schema จาก TypeScript decorator ในขณะ runtime โดยรักษา type และ schema ให้ซิงค์กันอัตโนมัติ Schema-first parse ไฟล์ SDL .graphql ซึ่งต้องกำหนด type ด้วยตนเอง Code-first เหมาะกับทีมที่ใช้ TypeScript เป็นหลัก schema-first ทำงานได้ดีกว่าเมื่อ schema เป็น contract ระหว่างทีม frontend และ backend
ถ: จะ implement permission ในระดับ field ได้อย่างไร?
มีสามแนวทาง: (1) @ResolveField() กับ return แบบมีเงื่อนไขตาม context ของ user, (2) custom decorator ที่ตรวจสอบ permission ก่อน resolve field, (3) schema directive เช่น @auth(requires: ADMIN) ที่ประมวลผลโดย directive transformer แนวทางแรกมีความยืดหยุ่นมากที่สุด directive ให้ documentation ของ schema ที่ชัดเจนที่สุด
ถ: อธิบาย GraphQL context ใน NestJS
object context ถูกส่งผ่านทุก resolver ภายใน request NestJS จะเติม HTTP request เป็นค่าเริ่มต้น context แบบกำหนดเองถูกตั้งค่าใน GraphQLModule.forRoot() ผ่านตัวเลือก context—มีประโยชน์สำหรับการเพิ่ม instance ของ DataLoader, user ที่ authenticate แล้ว หรือการเชื่อมต่อ database
ถ: subscription ถูก scale บนหลาย server instance ได้อย่างไร?
PubSub ในหน่วยความจำใช้งานได้เฉพาะการ deploy แบบ single-instance สถาปัตยกรรม multi-instance ต้องการ broker ภายนอก—Redis PubSub เป็นมาตรฐาน แต่ละ server instance subscribe ไปยัง Redis channel เมื่อ instance ใดก็ตาม publish event ทุก instance จะได้รับและส่งไปยัง WebSocket client ที่เชื่อมต่อ
สำหรับการเตรียมสัมภาษณ์ NestJS เพิ่มเติม ศึกษา module Middleware และ Interceptors ที่ครอบคลุมรูปแบบ request lifecycle
สรุป
- NestJS GraphQL รองรับทั้งแนวทาง code-first และ schema-first—code-first ทำให้โปรเจกต์ TypeScript ง่ายขึ้น schema-first เหมาะกับ workflow ที่ใช้ SDL
- DataLoader กำจัด N+1 query โดยรวมกลุ่ม request ของ field resolver ภายใน operation เดียว
- DataLoader instance ที่มี scope เป็น request ป้องกันการปนเปื้อน cache ระหว่าง request ที่เกิดขึ้นพร้อมกัน
GqlExecutionContextเชื่อมต่อ NestJS Guard กับ context ของ GraphQL resolver- subscription สำหรับ production ต้องการ Redis PubSub สำหรับการกระจาย event แบบ multi-instance
- การ authorize ในระดับ field รวม
@ResolveField()กับการตรวจสอบ context ของ user
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Microservices ด้วย NestJS ปี 2026: สถาปัตยกรรม, gRPC และคำถามสัมภาษณ์งาน
คู่มือฉบับสมบูรณ์เรื่องสถาปัตยกรรม NestJS Microservices กับ gRPC: transport layer, Protocol Buffers, streaming patterns และคำถามสัมภาษณ์งานสำหรับ backend engineer ปี 2026

NestJS + Prisma: สแตกแบ็กเอนด์สมัยใหม่สำหรับ Node.js
คู่มือฉบับสมบูรณ์ในการสร้าง API แบ็กเอนด์สมัยใหม่ด้วย NestJS และ Prisma ครอบคลุมการตั้งค่า โมเดล เซอร์วิส ทรานแซกชัน และแนวปฏิบัติที่ดี

NestJS: สร้าง REST API ที่สมบูรณ์ตั้งแต่เริ่มต้น
คู่มือฉบับสมบูรณ์สำหรับการสร้าง REST API ระดับมืออาชีพด้วย NestJS ครอบคลุม Controller, Service, Module, การตรวจสอบข้อมูลด้วย class-validator และการจัดการข้อผิดพลาด