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.

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.
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 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.
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 {}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.
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 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.
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) 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 memungkinkan validasi di tingkat schema dan 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;
}Aktifkan validasi secara global dengan menambahkan ValidationPipe di main.ts. Error GraphQL menyertakan pesan validasi di field extensions, menjaga kompatibilitas dengan client.
Siap menguasai wawancara Node.js / NestJS Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
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.
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 mengelompokkan dan meng-cache request dalam satu operasi GraphQL. Untuk NestJS, scope DataLoader ke level request menggunakan @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 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.
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.
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;
}
}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.
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.
@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 atau integrasi CASL bawaan NestJS. Artikel NestJS Guards and Interceptors 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 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
GqlExecutionContextmenjembatani 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
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Tag
Bagikan
Artikel terkait

Microservices dengan NestJS di 2026: Arsitektur, gRPC, dan Pertanyaan Wawancara
Panduan lengkap arsitektur microservices NestJS dengan gRPC: transport layer, Protocol Buffers, streaming patterns, dan pertanyaan wawancara untuk backend engineer di 2026.

NestJS + Prisma: stack backend modern untuk Node.js
Panduan lengkap untuk membangun API backend modern dengan NestJS dan Prisma. Setup, model, service, transaksi, dan praktik terbaik dijelaskan.

NestJS: Membangun REST API Lengkap dari Nol
Panduan lengkap membangun REST API profesional dengan NestJS. Controller, Service, Module, validasi dengan class-validator, dan penanganan error dijelaskan secara praktis.