# NestJS และ GraphQL ในปี 2026: Schema, Resolver และคำถามสัมภาษณ์ > คู่มือครบถ้วนสำหรับการผสานรวม NestJS GraphQL ด้วยแนวทาง schema-first และ code-first, รูปแบบ resolver และคำถามสัมภาษณ์สำหรับตำแหน่ง senior developer - Published: 2026-07-25 - Updated: 2026-07-25 - Author: SharpSkill - Tags: nestjs, graphql, nodejs, typescript, api, tutorial - Reading time: 12 min --- การผสานรวม NestJS GraphQL มอบแนวทางที่มีโครงสร้างในการสร้าง API ที่ type-safe โดยใช้ประโยชน์จาก decorator ของ TypeScript และภาษา query ของ GraphQL บทความนี้ครอบคลุมทั้งแนวทาง schema-first และ code-first, รูปแบบ resolver รวมถึงคำถามสัมภาษณ์ที่ hiring manager มักถามในปี 2026 > **Code-First vs Schema-First** > > NestJS 11 ใช้ GraphQL แบบ code-first เป็นค่าเริ่มต้น โดยสร้าง schema จาก TypeScript class แนวทาง schema-first ยังคงมีให้ใช้สำหรับทีมที่มีไฟล์ `.graphql` อยู่แล้วหรือ workflow ที่ใช้ SDL ## การตั้งค่า NestJS GraphQL กับ Apollo Server NestJS ผสานรวมกับ [Apollo Server](https://www.apollographql.com/docs/apollo-server/) ผ่าน package `@nestjs/graphql` การตั้งค่าจะแตกต่างกันตามแนวทางที่เลือก—code-first สร้าง SDL จาก decorator ในขณะที่ schema-first parse ไฟล์ `.graphql` โดยตรง ```typescript // app.module.ts import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { join } from 'path'; @Module({ imports: [ GraphQLModule.forRoot({ 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 ```typescript // user.entity.ts 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](https://graphql.org/learn/schema/#lists-and-non-null) อย่างแม่นยำ ## การสร้าง Resolver สำหรับ Query และ Mutation Resolver จัดการ operation ของ GraphQL ที่เข้ามา NestJS ใช้ `@Resolver()` เพื่อทำเครื่องหมาย class เป็น resolver โดยมี method decorator ระบุประเภทของ operation ```typescript // users.resolver.ts 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 { return this.usersService.findAll(); } @Query(() => User, { nullable: true }) user(@Args('id', { type: () => ID }) id: string): Promise { return this.usersService.findOne(id); } @Mutation(() => User) createUser(@Args('input') input: CreateUserInput): Promise { 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](https://github.com/typestack/class-validator) ช่วยให้สามารถ validate ทั้งในระดับ schema และ runtime ```typescript // dto/create-user.input.ts 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 ## การ Resolve ข้อมูลที่เกี่ยวข้องด้วย @ResolveField และ DataLoader Field resolver จัดการความสัมพันธ์ระหว่าง type หากไม่มีการเพิ่มประสิทธิภาพ การดึงรายชื่อ user พร้อมกับ post จะทำให้เกิด N+1 query—หนึ่งสำหรับ user แล้วอีกหนึ่งต่อ user สำหรับ post ```typescript // users.resolver.ts 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 { // DataLoader batches requests: one query for all user IDs return this.postsLoader.batchByUserId.load(user.id); } @ResolveField(() => Int) async postCount(@Parent() user: User): Promise { 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 })` ```typescript // posts/posts.loader.ts 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( 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(); 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](/technologies/node-nestjs/interview-questions/nestjs-modules-di) ## Subscription สำหรับข้อมูล Real-Time GraphQL subscription ส่งข้อมูลไปยัง client ผ่านการเชื่อมต่อ WebSocket NestJS ใช้ library `graphql-ws` ซึ่ง implement [โปรโตคอล GraphQL over WebSocket](https://github.com/enisdenjo/graphql-ws) ```typescript // app.module.ts - Enable subscriptions GraphQLModule.forRoot({ driver: ApolloDriver, autoSchemaFile: true, subscriptions: { 'graphql-ws': true, // Modern protocol 'subscriptions-transport-ws': false, // Deprecated legacy protocol }, }), ``` ```typescript // posts.resolver.ts 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 { 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 ```typescript // guards/gql-auth.guard.ts 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 ```typescript // users.resolver.ts @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](https://github.com/dimatill/graphql-shield) หรือการผสานรวม CASL ที่มีอยู่ใน NestJS บทความ [NestJS Guards and Interceptors](/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture) อธิบายรูปแบบเหล่านี้อย่างละเอียด ## คำถามสัมภาษณ์ 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](/technologies/node-nestjs/interview-questions/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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/node-nestjs/nestjs-graphql-schemas-resolvers-tutorial