Skip to main content
Practice Problems

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 graphql
typescript
// 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

FeatureRESTGraphQL
FetchingMultiple endpointsSingle endpoint
Over-fetchingCommonClient specifies fields
Under-fetchingMultiple requestsOne request
VersioningURL-based (v1, v2)Schema evolution
CachingHTTP cachingMore complex
Learning curveLowerHigher

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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems