Learn NestJS - GraphQL & API Integration
Episode 15 of 24

Learn NestJS - GraphQL & API Integration

This episode covers GraphQL integration in NestJS: GraphQL basics with @nestjs/graphql, schema-first and code-first approaches, creating resolvers, input types, and subscriptions, plus GraphQL performance optimization with batching and caching.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

REST APIs sometimes force clients to make many requests for a single view. GraphQL offers a solution: one endpoint, and the client decides exactly what data it needs. Episode 15 covers GraphQL basics in NestJS: schema approaches, resolvers, input types, subscriptions, and performance optimization.

GraphQL Basics with @nestjs/graphql

Install the Packages

Install GraphQL dan Apollo
npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express

Setting Up GraphQLModule

JSMengaktifkan GraphQLModule
import { Module } from "@nestjs/common";
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
import { GraphQLModule } from "@nestjs/graphql";
 
@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: true,
    }),
  ],
})
export class AppModule {}

With autoSchemaFile: true, NestJS generates the schema automatically from your code — this is the code-first approach.

Schema-First vs Code-First

Code-First

The code-first approach defines the schema through TypeScript decorators. Its advantages: schema and code are always in sync, and there's type safety.

JSObjectType dengan dekorator
import { Field, ID, ObjectType } from "@nestjs/graphql";
 
@ObjectType()
export class User {
  @Field(() => ID)
  id: number;
 
  @Field()
  name: string;
 
  @Field()
  email: string;
}

The @ObjectType and @Field decorators generate the GraphQL schema automatically.

Schema-First

The schema-first approach writes the schema in a .graphql file first, then generates TypeScript from it. It suits teams that want the schema agreed upon first, for example as a contract between frontend and backend teams.

Resolvers, Queries, and Mutations

Creating a Resolver

A resolver is a method that handles GraphQL queries and mutations:

JSResolver user
import { Args, Mutation, Query, Resolver } from "@nestjs/graphql";
import { User } from "./user.model";
import { CreateUserInput } from "./create-user.input";
 
@Resolver(() => User)
export class UserResolver {
  @Query(() => [User])
  users(): User[] {
    return [
      { id: 1, name: "Arman", email: "arman@example.com" },
    ];
  }
 
  @Mutation(() => User)
  createUser(@Args("input") input: CreateUserInput): User {
    return { id: 2, name: input.name, email: input.email };
  }
}

@Query handles reads, @Mutation handles data changes.

Input Types

For structured input, use @InputType:

JSInputType untuk mutation
import { Field, InputType } from "@nestjs/graphql";
import { IsEmail, IsString, MinLength } from "class-validator";
 
@InputType()
export class CreateUserInput {
  @Field()
  @IsString()
  @MinLength(3)
  name: string;
 
  @Field()
  @IsEmail()
  email: string;
}

Input types can also be validated with class-validator, just like DTOs in REST.

Subscriptions

Real-time with Subscriptions

Subscriptions allow clients to receive real-time updates:

JSSubscription sederhana
import { Subscription } from "@nestjs/graphql";
import { PubSub } from "graphql-subscriptions";
 
@Resolver(() => User)
export class UserResolver {
  constructor(private readonly pubSub: PubSub) {}
 
  @Subscription(() => User)
  userCreated(): AsyncIterator<User> {
    return this.pubSub.asyncIterator("userCreated");
  }
}

The publisher calls pubSub.publish("userCreated", { userCreated: user }) when a new user is created, and subscribed clients receive the notification directly.

GraphQL Performance Optimization

DataLoader for Batching

A common GraphQL problem is the N+1 query — for example loading the author for every post. The solution is DataLoader, which performs batching and caching:

JSDataLoader untuk author
import * as DataLoader from "dataloader";
 
const authorLoader = new DataLoader(async (ids: readonly number[]) => {
  const authors = await findAuthorsByIds([...ids]);
  return ids.map((id) => authors.find((a) => a.id === id));
});

With batching, many small requests are combined into a single database query. Resolvers can also be cached with the CacheInterceptor to reduce database load — combining batching and caching keeps your GraphQL API fast even as query complexity grows.

Conclusion

Episode 15 introduces GraphQL in NestJS: basic setup, schema-first and code-first approaches, resolvers and input types, subscriptions, and optimization with batching and caching.

Key takeaways:

  • GraphQL provides one endpoint with client-side data control.
  • Code-first defines the schema through TypeScript decorators.
  • @Query, @Mutation, and @Subscription are the three operation types.
  • Subscriptions send real-time updates to clients.
  • DataLoader combines queries to avoid the N+1 problem.