How to build a GraphQL API with NestJS?
GraphQL in NestJS
NestJS provides first-class GraphQL support through the @nestjs/graphql package, supporting both code-first and schema-first approaches.
Setup (Code-First)
bash
npm install @nestjs/graphql @nestjs/apollo @apollo/server graphqltypescript
// app.module.ts
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
@Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'schema.gql', // Auto-generate schema
sortSchema: true,
playground: true,
}),
],
})
export class AppModule {}Object Types
typescript
import { ObjectType, Field, ID, Int } from '@nestjs/graphql';
@ObjectType()
export class User {
@Field(() => ID)
id: string;
@Field()
name: string;
@Field()
email: string;
@Field(() => Int)
age: number;
@Field(() => [Post], { nullable: true })
posts?: Post[];
@Field()
createdAt: Date;
}Input Types
typescript
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class CreateUserInput {
@Field()
@IsString()
name: string;
@Field()
@IsEmail()
email: string;
@Field(() => Int)
@Min(18)
age: number;
}
@InputType()
export class UpdateUserInput extends PartialType(CreateUserInput) {}Resolvers (like Controllers)
typescript
import { Resolver, Query, Mutation, Args, ResolveField, Parent } from '@nestjs/graphql';
@Resolver(() => User)
export class UsersResolver {
constructor(
private usersService: UsersService,
private postsService: PostsService,
) {}
@Query(() => [User], { name: 'users' })
findAll() {
return this.usersService.findAll();
}
@Query(() => User, { name: 'user' })
findOne(@Args('id', { type: () => ID }) id: string) {
return this.usersService.findOne(id);
}
@Mutation(() => User)
createUser(@Args('input') input: CreateUserInput) {
return this.usersService.create(input);
}
@Mutation(() => User)
updateUser(
@Args('id', { type: () => ID }) id: string,
@Args('input') input: UpdateUserInput,
) {
return this.usersService.update(id, input);
}
@Mutation(() => Boolean)
deleteUser(@Args('id', { type: () => ID }) id: string) {
return this.usersService.delete(id);
}
// Resolve nested field
@ResolveField(() => [Post])
posts(@Parent() user: User) {
return this.postsService.findByUserId(user.id);
}
}Authentication in GraphQL
typescript
@Resolver(() => User)
export class UsersResolver {
@Query(() => User)
@UseGuards(GqlAuthGuard)
me(@CurrentUser() user: User) {
return user;
}
}
// GQL Auth Guard
@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}Subscriptions (Real-time)
typescript
@Resolver(() => Message)
export class MessagesResolver {
@Subscription(() => Message, {
filter: (payload, variables) =>
payload.messageAdded.roomId === variables.roomId,
})
messageAdded(@Args('roomId') roomId: string) {
return pubSub.asyncIterator('messageAdded');
}
@Mutation(() => Message)
async sendMessage(@Args('input') input: SendMessageInput) {
const message = await this.messagesService.create(input);
pubSub.publish('messageAdded', { messageAdded: message });
return message;
}
}DataLoader (N+1 Problem)
typescript
import DataLoader from 'dataloader';
@Injectable({ scope: Scope.REQUEST })
export class PostsLoader {
constructor(private postsService: PostsService) {}
readonly batchPosts = new DataLoader<string, Post[]>(
async (userIds: string[]) => {
const posts = await this.postsService.findByUserIds([...userIds]);
return userIds.map((id) => posts.filter((p) => p.userId === id));
},
);
}
// In resolver
@ResolveField(() => [Post])
posts(@Parent() user: User) {
return this.postsLoader.batchPosts.load(user.id);
}GraphQL vs REST
| Feature | REST | GraphQL |
|---|---|---|
| Fetching | Multiple endpoints | Single endpoint |
| Over-fetching | Common | Client specifies fields |
| Under-fetching | Multiple requests | One request |
| Versioning | URL-based (v1, v2) | Schema evolution |
| Caching | HTTP caching | More complex |
| Learning curve | Lower | Higher |
Tip: Use GraphQL when your frontend needs flexible data fetching, or when you have multiple clients (web, mobile) with different data requirements. Use REST for simpler APIs or when HTTP caching is important.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.