Back to Articles
45 min read

Designing Modern APIs in NestJS: From REST and OpenAPI to GraphQL Federation and gRPC

Modern backends require versatility. Whether you need a standard RESTful interface documented with OpenAPI, a high-performance GraphQL graph utilizing Federation, or low-latency gRPC microservices, NestJS provides the abstractions. This guide compares, contrasts, and implements every major communication protocol available in the framework.

API Development

RESTful API Design

NestJS provides decorators that map directly to REST principles, making it intuitive to build resource-based APIs with proper HTTP methods, status codes, and URL structures.

@Controller('users') export class UsersController { @Post() // POST /users @HttpCode(201) create(@Body() dto: CreateUserDto) { } @Get() // GET /users findAll(@Query() query: PaginationDto) { } @Get(':id') // GET /users/:id findOne(@Param('id') id: string) { } @Patch(':id') // PATCH /users/:id update(@Param('id') id: string, @Body() dto: UpdateUserDto) { } @Delete(':id') // DELETE /users/:id @HttpCode(204) remove(@Param('id') id: string) { } }

OpenAPI/Swagger Documentation

The @nestjs/swagger package auto-generates interactive API documentation from your controllers and DTOs using decorators, providing a self-documenting API that stays in sync with your code.

// main.ts const config = new DocumentBuilder() .setTitle('My API') .setVersion('1.0') .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, document); // dto export class CreateUserDto { @ApiProperty({ example: 'john@example.com' }) @IsEmail() email: string; }

GraphQL with Apollo

NestJS integrates Apollo Server for GraphQL, supporting both code-first (decorators) and schema-first approaches, with automatic schema generation and playground for development.

// app.module.ts GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, autoSchemaFile: join(process.cwd(), 'src/schema.gql'), }), // user.resolver.ts @Resolver(() => User) export class UserResolver { @Query(() => [User]) users() { return this.userService.findAll(); } @Mutation(() => User) createUser(@Args('input') input: CreateUserInput) { return this.userService.create(input); } }

GraphQL with Mercurius

Mercurius is a high-performance GraphQL adapter for Fastify, offering better performance than Apollo in benchmarks, with support for JIT compilation and federation.

// app.module.ts GraphQLModule.forRoot<MercuriusDriverConfig>({ driver: MercuriusDriver, autoSchemaFile: true, graphiql: true, subscription: true, }), // Resolvers remain the same as Apollo! @Resolver(() => User) export class UserResolver { /* identical code */ }

GraphQL Subscriptions

Subscriptions enable real-time data push over WebSocket connections, perfect for live updates like notifications, chat messages, or live feeds using pub/sub patterns.

@Resolver(() => Message) export class MessageResolver { constructor(private pubSub: PubSub) {} @Mutation(() => Message) async sendMessage(@Args('input') input: SendMessageInput) { const message = await this.messageService.create(input); this.pubSub.publish('messageAdded', { messageAdded: message }); return message; } @Subscription(() => Message) messageAdded() { return this.pubSub.asyncIterator('messageAdded'); } }
┌─────────┐  WebSocket  ┌─────────┐  publish  ┌────────┐
│ Client  │◄────────────│ Server  │◄──────────│ PubSub │
└─────────┘  subscribe  └─────────┘           └────────┘

GraphQL Federation

Federation allows splitting a GraphQL schema across multiple services, enabling microservices architecture where each service owns its portion of the graph.

// Users service (subgraph) @Directive('@key(fields: "id")') @ObjectType() export class User { @Field() id: string; } // Orders service (extends User) @ObjectType() @Directive('@extends') @Directive('@key(fields: "id")') export class User { @Directive('@external') @Field() id: string; @Field(() => [Order]) orders: Order[]; }
        ┌───────────────────┐
        │   Apollo Gateway  │
        └─────────┬─────────┘
    ┌─────────────┼─────────────┐
    ▼             ▼             ▼
┌───────┐   ┌──────────┐   ┌─────────┐
│ Users │   │ Products │   │ Orders  │
└───────┘   └──────────┘   └─────────┘

gRPC

gRPC provides high-performance binary communication between microservices using Protocol Buffers, with automatic code generation for type-safe client/server implementations.

// hero.proto service HeroService { rpc FindOne (HeroById) returns (Hero); } message HeroById { int32 id = 1; } message Hero { int32 id = 1; string name = 2; }
@Controller() export class HeroController { @GrpcMethod('HeroService', 'FindOne') findOne(data: HeroById): Hero { return { id: data.id, name: 'John' }; } }

WebSockets

NestJS provides WebSocket support via Gateways using Socket.io or native WebSockets, enabling bidirectional real-time communication for chat apps, live updates, and gaming.

@WebSocketGateway({ cors: true }) export class EventsGateway { @WebSocketServer() server: Server; @SubscribeMessage('message') handleMessage(@MessageBody() data: string): WsResponse<string> { return { event: 'message', data: `Echo: ${data}` }; } broadcast(event: string, data: any) { this.server.emit(event, data); } }

Server-Sent Events (SSE)

SSE provides one-way real-time updates from server to client over HTTP, simpler than WebSockets for scenarios like live feeds, progress updates, or notifications.

@Controller('events') export class EventsController { @Sse('stream') stream(): Observable<MessageEvent> { return interval(1000).pipe( map((num) => ({ data: { timestamp: Date.now(), count: num }, } as MessageEvent)), ); } }
Client                     Server
  │                          │
  │──── GET /events/stream ──►│
  │                          │
  │◄──── data: {...} ────────│
  │◄──── data: {...} ────────│
  │◄──── data: {...} ────────│
  │         (continuous)     │

Hybrid Applications

Hybrid applications combine multiple transport layers (HTTP, microservices, WebSockets) in a single NestJS application, enabling flexible communication patterns.

// main.ts async function bootstrap() { const app = await NestFactory.create(AppModule); // Add microservice transport app.connectMicroservice<MicroserviceOptions>({ transport: Transport.REDIS, options: { host: 'localhost', port: 6379 }, }); // Add gRPC transport app.connectMicroservice<MicroserviceOptions>({ transport: Transport.GRPC, options: { package: 'hero', protoPath: 'hero.proto' }, }); await app.startAllMicroservices(); await app.listen(3000); // HTTP still works }