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.

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.
npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-expressimport { 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.
The code-first approach defines the schema through TypeScript decorators. Its advantages: schema and code are always in sync, and there's type safety.
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.
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.
A resolver is a method that handles GraphQL queries and mutations:
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.
For structured input, use @InputType:
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 allow clients to receive real-time updates:
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.
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:
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.
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:
@Query, @Mutation, and @Subscription are the three operation types.