# NestJS dan GraphQL di Tahun 2026: Schema, Resolver, dan Pertanyaan Interview > Panduan lengkap integrasi NestJS GraphQL dengan pendekatan schema-first dan code-first, pola resolver, serta pertanyaan interview untuk posisi senior developer. - Published: 2026-07-25 - Updated: 2026-07-25 - Author: SharpSkill - Tags: nestjs, graphql, nodejs, typescript, api, tutorial - Reading time: 12 min --- Integrasi NestJS GraphQL menyediakan pendekatan terstruktur untuk membangun API yang type-safe dengan memanfaatkan decorator TypeScript dan bahasa query GraphQL. Tutorial ini membahas pendekatan schema-first dan code-first, pola resolver, serta pertanyaan interview yang sering diajukan hiring manager di tahun 2026. > **Code-First vs Schema-First** > > NestJS 11 secara default menggunakan GraphQL code-first, yang menghasilkan schema dari class TypeScript. Schema-first tetap tersedia untuk tim yang sudah memiliki file `.graphql` atau workflow berbasis SDL. ## Konfigurasi NestJS GraphQL dengan Apollo Server NestJS terintegrasi dengan [Apollo Server](https://www.apollographql.com/docs/apollo-server/) melalui package `@nestjs/graphql`. Konfigurasi berbeda berdasarkan pendekatan yang dipilih—code-first menghasilkan SDL dari decorator, sementara schema-first mem-parse file `.graphql` secara langsung. ```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 {} ``` Opsi `autoSchemaFile` mengaktifkan mode code-first. Jika diatur ke `true`, schema akan dihasilkan di memory tanpa menulis ke disk—berguna untuk deployment serverless di mana akses filesystem mungkin terbatas. ## Mendefinisikan Tipe GraphQL dengan Decorator Code-First Code-first mendefinisikan tipe GraphQL menggunakan class TypeScript yang didekorasi dengan `@ObjectType()`. Setiap field menggunakan `@Field()` untuk menentukan tipe GraphQL dan nullability-nya. ```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; } ``` Opsi `nullable` menerima tiga nilai: `true` (field bersifat opsional), `'items'` (item dalam list bisa null), dan `'itemsAndList'` (baik list maupun item bisa null). Granularitas ini sesuai dengan [semantik nullability GraphQL](https://graphql.org/learn/schema/#lists-and-non-null) secara presisi. ## Membangun Resolver untuk Query dan Mutation Resolver menangani operasi GraphQL yang masuk. NestJS menggunakan `@Resolver()` untuk menandai class sebagai resolver, dengan method decorator yang menentukan tipe operasi. ```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)` menetapkan konteks untuk decorator `@ResolveField()`, memungkinkan resolusi tingkat field untuk data yang dihitung atau terkait. ## Input Type dan Validasi dengan class-validator Input type GraphQL mendefinisikan payload mutation. Menggabungkan `@InputType()` dengan decorator [class-validator](https://github.com/typestack/class-validator) memungkinkan validasi di tingkat schema dan 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; } ``` Aktifkan validasi secara global dengan menambahkan `ValidationPipe` di `main.ts`. Error GraphQL menyertakan pesan validasi di field `extensions`, menjaga kompatibilitas dengan client. ## Menyelesaikan Data Terkait dengan @ResolveField dan DataLoader Field resolver menangani relasi antar tipe. Tanpa optimasi, mengambil daftar user beserta post-nya akan memicu N+1 query—satu untuk user, lalu satu per user untuk 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 mengelompokkan dan meng-cache request dalam satu operasi GraphQL. Untuk NestJS, scope DataLoader ke level request menggunakan `@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 mengurangi N+1 query menjadi satu query batch, sangat penting untuk API GraphQL di mana client mengontrol kedalaman query. Untuk pembahasan lebih mendalam tentang pola arsitektur NestJS, lihat [pertanyaan interview NestJS Modules & Dependency Injection](/technologies/node-nestjs/interview-questions/nestjs-modules-di). ## Subscription untuk Data Real-Time Subscription GraphQL mengirimkan data ke client melalui koneksi WebSocket. NestJS menggunakan library `graphql-ws`, yang mengimplementasikan [protokol 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; } } ``` Untuk deployment produksi di beberapa instance, ganti `PubSub` in-memory dengan `graphql-redis-subscriptions` untuk menyiarkan event ke seluruh cluster. ## Autentikasi dan Autorisasi di GraphQL NestJS Guard bekerja dengan lancar dengan GraphQL resolver. Execution context berbeda dari REST—gunakan `GqlExecutionContext` untuk mengekstrak 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 } } ``` Terapkan guard di level resolver atau method. Autorisasi tingkat field menggunakan `@ResolveField()` dengan logika kondisional berdasarkan 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; } } ``` Untuk autorisasi yang kompleks, pertimbangkan [GraphQL Shield](https://github.com/dimatill/graphql-shield) atau integrasi CASL bawaan NestJS. Artikel [NestJS Guards and Interceptors](/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture) membahas pola-pola ini secara mendalam. ## Pertanyaan Interview NestJS GraphQL yang Umum Interview teknis untuk posisi NestJS sering menyertakan pertanyaan spesifik tentang GraphQL. Berikut adalah pola-pola yang dievaluasi oleh hiring manager. **T: Bagaimana NestJS menangani masalah N+1 di GraphQL?** DataLoader mengelompokkan panggilan field resolver dalam satu request. Ketika beberapa objek parent meminta field yang sama, DataLoader mengumpulkan semua key, mengeksekusi satu query batch, dan mendistribusikan hasilnya. Loader harus ber-scope request untuk mencegah masalah caching lintas request. **T: Apa perbedaan antara code-first dan schema-first di NestJS GraphQL?** Code-first menghasilkan schema GraphQL dari decorator TypeScript saat runtime, menjaga tipe dan schema tetap sinkron secara otomatis. Schema-first mem-parse file SDL `.graphql`, memerlukan definisi tipe manual. Code-first cocok untuk tim yang native TypeScript; schema-first lebih baik ketika schema menjadi kontrak antara tim frontend dan backend. **T: Bagaimana cara mengimplementasikan permission tingkat field?** Ada tiga pendekatan: (1) `@ResolveField()` dengan return kondisional berdasarkan konteks user, (2) Custom decorator yang memeriksa permission sebelum resolusi field, (3) Schema directive seperti `@auth(requires: ADMIN)` yang diproses oleh directive transformer. Pendekatan pertama menawarkan fleksibilitas paling tinggi; directive memberikan dokumentasi schema yang paling bersih. **T: Jelaskan GraphQL context di NestJS.** Objek context melewati semua resolver dalam satu request. NestJS mengisinya dengan HTTP request secara default. Context kustom dikonfigurasi di `GraphQLModule.forRoot()` melalui opsi `context`—berguna untuk menambahkan instance DataLoader, user yang terautentikasi, atau koneksi database. **T: Bagaimana subscription diskalakan di beberapa instance server?** PubSub in-memory hanya berfungsi untuk deployment single-instance. Arsitektur multi-instance memerlukan broker eksternal—Redis PubSub adalah standarnya. Setiap instance server subscribe ke channel Redis; ketika instance mana pun mempublikasikan event, semua instance menerimanya dan push ke client WebSocket yang terhubung. Untuk persiapan interview NestJS tambahan, eksplorasi [modul Middleware dan Interceptors](/technologies/node-nestjs/interview-questions/middleware-interceptors) yang membahas pola request lifecycle. ## Kesimpulan - NestJS GraphQL mendukung pendekatan code-first dan schema-first—code-first menyederhanakan proyek TypeScript, schema-first cocok untuk workflow berbasis SDL - DataLoader mengeliminasi N+1 query dengan mengelompokkan request field resolver dalam satu operasi - Instance DataLoader dengan scope request mencegah polusi cache di antara request konkuren - `GqlExecutionContext` menjembatani NestJS Guard dengan konteks resolver GraphQL - Subscription produksi memerlukan Redis PubSub untuk distribusi event multi-instance - Autorisasi tingkat field menggabungkan `@ResolveField()` dengan pemeriksaan konteks user --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/node-nestjs/nestjs-graphql-schemas-resolvers-tutorial