@nestjsvn/swagger-sse
Version:
OpenAPI documentation and interactive Swagger UI for NestJS Server-Sent Events endpoints
819 lines (726 loc) • 21.4 kB
Markdown
# Basic Usage Examples
This document provides practical examples of using `@nestjsvn/swagger-sse` in real-world scenarios.
## Table of Contents
1. [Simple Event Stream](#simple-event-stream)
2. [Multi-Event Type Stream](#multi-event-type-stream)
3. [Authenticated Streams](#authenticated-streams)
4. [Error Handling](#error-handling)
5. [Query Parameter Filtering](#query-parameter-filtering)
6. [Real-time Chat Application](#real-time-chat-application)
7. [System Monitoring Dashboard](#system-monitoring-dashboard)
8. [E-commerce Order Tracking](#e-commerce-order-tracking)
## Simple Event Stream
Basic SSE endpoint that emits periodic events:
```typescript
// dto/notification.dto.ts
export class NotificationDto {
id: string;
title: string;
message: string;
type: 'info' | 'warning' | 'error' | 'success';
timestamp: Date;
}
// controllers/notifications.controller.ts
import { Controller, Sse } from '@nestjs/common';
import { Observable, interval, map } from 'rxjs';
import { ApiSse } from '@nestjsvn/swagger-sse';
import { NotificationDto } from '../dto/notification.dto';
@Controller('notifications')
export class NotificationsController {
@Sse('stream')
@ApiSse({
summary: 'Notification stream',
description: 'Real-time notifications for the application',
events: {
'notification': NotificationDto,
},
})
streamNotifications(): Observable<MessageEvent> {
return interval(3000).pipe(
map((i) => ({
data: JSON.stringify({
id: `notif-${i}`,
title: `Notification ${i}`,
message: `This is notification number ${i}`,
type: i % 4 === 0 ? 'error' : i % 3 === 0 ? 'warning' : i % 2 === 0 ? 'success' : 'info',
timestamp: new Date(),
}),
type: 'notification',
} as MessageEvent))
);
}
}
```
## Multi-Event Type Stream
Stream that emits different types of events:
```typescript
// dto/user-events.dto.ts
export class UserCreatedDto {
userId: string;
username: string;
email: string;
createdAt: Date;
}
export class UserUpdatedDto {
userId: string;
username: string;
email: string;
updatedAt: Date;
changes: string[];
}
export class UserDeletedDto {
userId: string;
deletedAt: Date;
reason?: string;
}
// controllers/user-events.controller.ts
import { Controller, Sse } from '@nestjs/common';
import { Observable, merge, interval, map, switchMap, of } from 'rxjs';
import { ApiSse } from '@nestjsvn/swagger-sse';
@Controller('user-events')
export class UserEventsController {
@Sse('stream')
@ApiSse({
summary: 'User lifecycle events',
description: 'Stream of user creation, update, and deletion events',
events: {
'user-created': UserCreatedDto,
'user-updated': UserUpdatedDto,
'user-deleted': UserDeletedDto,
},
})
streamUserEvents(): Observable<MessageEvent> {
// Simulate different event types
const createEvents = interval(5000).pipe(
map((i) => ({
data: JSON.stringify({
userId: `user-${i}`,
username: `user${i}`,
email: `user${i}@example.com`,
createdAt: new Date(),
}),
type: 'user-created',
} as MessageEvent))
);
const updateEvents = interval(7000).pipe(
map((i) => ({
data: JSON.stringify({
userId: `user-${i}`,
username: `updated-user${i}`,
email: `updated-user${i}@example.com`,
updatedAt: new Date(),
changes: ['username', 'email'],
}),
type: 'user-updated',
} as MessageEvent))
);
const deleteEvents = interval(15000).pipe(
map((i) => ({
data: JSON.stringify({
userId: `user-${i}`,
deletedAt: new Date(),
reason: 'Account deactivated',
}),
type: 'user-deleted',
} as MessageEvent))
);
return merge(createEvents, updateEvents, deleteEvents);
}
}
```
## Authenticated Streams
SSE endpoint that requires authentication:
```typescript
// guards/jwt-auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
return false;
}
try {
const payload = this.jwtService.verify(token);
request.user = payload;
return true;
} catch {
return false;
}
}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
// dto/private-event.dto.ts
export class PrivateEventDto {
eventId: string;
userId: string;
eventType: string;
data: any;
timestamp: Date;
}
// controllers/private-events.controller.ts
import { Controller, Sse, UseGuards, Request } from '@nestjs/common';
import { Observable, interval, map, filter } from 'rxjs';
import { ApiSse } from '@nestjsvn/swagger-sse';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { PrivateEventDto } from '../dto/private-event.dto';
@Controller('private-events')
@ApiTags('Private Events')
export class PrivateEventsController {
@Sse('user-specific')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiSse({
summary: 'User-specific event stream',
description: 'Private events stream for authenticated users',
security: [{ bearer: [] }],
events: {
'private-event': PrivateEventDto,
},
})
streamPrivateEvents(@Request() req): Observable<MessageEvent> {
const userId = req.user.sub;
return interval(2000).pipe(
map((i) => ({
data: JSON.stringify({
eventId: `event-${userId}-${i}`,
userId: userId,
eventType: 'user-activity',
data: { action: 'page-view', page: `/dashboard/${i}` },
timestamp: new Date(),
}),
type: 'private-event',
} as MessageEvent))
);
}
}
```
## Error Handling
Stream with comprehensive error handling:
```typescript
// dto/error-event.dto.ts
export class ErrorEventDto {
errorId: string;
message: string;
code: number;
timestamp: Date;
context?: any;
}
export class DataEventDto {
id: string;
data: any;
timestamp: Date;
}
// services/data.service.ts
import { Injectable } from '@nestjs/common';
import { Observable, throwError, of, interval } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
@Injectable()
export class DataService {
getData(): Observable<any> {
return interval(1000).pipe(
switchMap((i) => {
// Simulate random errors
if (i > 0 && i % 10 === 0) {
return throwError(() => new Error(`Simulated error at interval ${i}`));
}
return of({ value: i, timestamp: new Date() });
})
);
}
}
// controllers/data-stream.controller.ts
import { Controller, Sse } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { map, catchError, startWith } from 'rxjs/operators';
import { ApiSse } from '@nestjsvn/swagger-sse';
import { DataService } from '../services/data.service';
@Controller('data-stream')
export class DataStreamController {
constructor(private dataService: DataService) {}
@Sse('with-errors')
@ApiSse({
summary: 'Data stream with error handling',
description: 'Stream that demonstrates error handling patterns',
events: {
'data-event': DataEventDto,
'error-event': ErrorEventDto,
},
})
streamWithErrorHandling(): Observable<MessageEvent> {
return this.dataService.getData().pipe(
map((data) => ({
data: JSON.stringify({
id: `data-${Date.now()}`,
data: data,
timestamp: new Date(),
}),
type: 'data-event',
} as MessageEvent)),
catchError((error) => {
console.error('Stream error:', error);
return of({
data: JSON.stringify({
errorId: `error-${Date.now()}`,
message: error.message,
code: 500,
timestamp: new Date(),
context: { source: 'data-service' },
}),
type: 'error-event',
} as MessageEvent);
}),
startWith({
data: JSON.stringify({
id: 'stream-start',
data: { message: 'Stream started' },
timestamp: new Date(),
}),
type: 'data-event',
} as MessageEvent)
);
}
}
```
## Query Parameter Filtering
Stream that supports filtering via query parameters:
```typescript
// controllers/filtered-events.controller.ts
import { Controller, Sse, Query } from '@nestjs/common';
import { Observable, interval, map, filter } from 'rxjs';
import { ApiSse } from '@nestjsvn/swagger-sse';
import { ApiQuery } from '@nestjs/swagger';
@Controller('filtered-events')
export class FilteredEventsController {
@Sse('stream')
@ApiQuery({ name: 'category', required: false, description: 'Filter events by category' })
@ApiQuery({ name: 'priority', required: false, description: 'Filter events by priority level' })
@ApiSse({
summary: 'Filtered event stream',
description: 'Stream events filtered by query parameters',
events: {
'filtered-event': {
id: 'string',
category: 'string',
priority: 'number',
message: 'string',
timestamp: 'Date',
},
},
})
streamFilteredEvents(
@Query('category') category?: string,
@Query('priority') priority?: string,
): Observable<MessageEvent> {
const priorityNum = priority ? parseInt(priority, 10) : undefined;
return interval(1000).pipe(
map((i) => {
const eventCategory = ['system', 'user', 'security', 'performance'][i % 4];
const eventPriority = (i % 5) + 1;
return {
id: `event-${i}`,
category: eventCategory,
priority: eventPriority,
message: `Event ${i} in ${eventCategory} with priority ${eventPriority}`,
timestamp: new Date(),
};
}),
filter((event) => {
if (category && event.category !== category) return false;
if (priorityNum && event.priority !== priorityNum) return false;
return true;
}),
map((event) => ({
data: JSON.stringify(event),
type: 'filtered-event',
} as MessageEvent))
);
}
}
```
## Real-time Chat Application
Complete chat application with SSE:
```typescript
// dto/chat.dto.ts
export class ChatMessageDto {
messageId: string;
roomId: string;
userId: string;
username: string;
message: string;
timestamp: Date;
}
export class UserJoinedDto {
userId: string;
username: string;
roomId: string;
timestamp: Date;
}
export class UserLeftDto {
userId: string;
username: string;
roomId: string;
timestamp: Date;
}
// services/chat.service.ts
import { Injectable } from '@nestjs/common';
import { Subject, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Injectable()
export class ChatService {
private eventSubject = new Subject<any>();
sendMessage(roomId: string, userId: string, username: string, message: string) {
this.eventSubject.next({
type: 'chat-message',
data: {
messageId: `msg-${Date.now()}`,
roomId,
userId,
username,
message,
timestamp: new Date(),
},
});
}
userJoined(roomId: string, userId: string, username: string) {
this.eventSubject.next({
type: 'user-joined',
data: {
userId,
username,
roomId,
timestamp: new Date(),
},
});
}
userLeft(roomId: string, userId: string, username: string) {
this.eventSubject.next({
type: 'user-left',
data: {
userId,
username,
roomId,
timestamp: new Date(),
},
});
}
getEventsForRoom(roomId: string): Observable<MessageEvent> {
return this.eventSubject.pipe(
filter((event) => event.data.roomId === roomId),
map((event) => ({
data: JSON.stringify(event.data),
type: event.type,
} as MessageEvent))
);
}
}
// controllers/chat.controller.ts
import { Controller, Sse, Query, Post, Body } from '@nestjs/common';
import { Observable } from 'rxjs';
import { ApiSse } from '@nestjsvn/swagger-sse';
import { ApiQuery, ApiBody, ApiTags } from '@nestjs/swagger';
import { ChatService } from '../services/chat.service';
@Controller('chat')
@ApiTags('Chat')
export class ChatController {
constructor(private chatService: ChatService) {}
@Sse('room/:roomId')
@ApiQuery({ name: 'roomId', description: 'Chat room ID' })
@ApiSse({
summary: 'Chat room event stream',
description: 'Real-time chat events for a specific room',
events: {
'chat-message': ChatMessageDto,
'user-joined': UserJoinedDto,
'user-left': UserLeftDto,
},
})
streamChatEvents(@Query('roomId') roomId: string): Observable<MessageEvent> {
return this.chatService.getEventsForRoom(roomId);
}
@Post('send-message')
@ApiBody({
schema: {
type: 'object',
properties: {
roomId: { type: 'string' },
userId: { type: 'string' },
username: { type: 'string' },
message: { type: 'string' },
},
},
})
sendMessage(@Body() body: any) {
this.chatService.sendMessage(body.roomId, body.userId, body.username, body.message);
return { success: true };
}
@Post('join-room')
@ApiBody({
schema: {
type: 'object',
properties: {
roomId: { type: 'string' },
userId: { type: 'string' },
username: { type: 'string' },
},
},
})
joinRoom(@Body() body: any) {
this.chatService.userJoined(body.roomId, body.userId, body.username);
return { success: true };
}
}
```
## System Monitoring Dashboard
Real-time system metrics streaming:
```typescript
// dto/metrics.dto.ts
export class CpuMetricDto {
timestamp: Date;
usage: number;
cores: number;
}
export class MemoryMetricDto {
timestamp: Date;
used: number;
total: number;
percentage: number;
}
export class NetworkMetricDto {
timestamp: Date;
bytesIn: number;
bytesOut: number;
packetsIn: number;
packetsOut: number;
}
// services/metrics.service.ts
import { Injectable } from '@nestjs/common';
import { Observable, interval } from 'rxjs';
import { map } from 'rxjs/operators';
import * as os from 'os';
@Injectable()
export class MetricsService {
getCpuMetrics(): Observable<CpuMetricDto> {
return interval(1000).pipe(
map(() => ({
timestamp: new Date(),
usage: Math.random() * 100,
cores: os.cpus().length,
}))
);
}
getMemoryMetrics(): Observable<MemoryMetricDto> {
return interval(2000).pipe(
map(() => {
const total = os.totalmem();
const free = os.freemem();
const used = total - free;
return {
timestamp: new Date(),
used: used,
total: total,
percentage: (used / total) * 100,
};
})
);
}
getNetworkMetrics(): Observable<NetworkMetricDto> {
return interval(3000).pipe(
map(() => ({
timestamp: new Date(),
bytesIn: Math.floor(Math.random() * 1000000),
bytesOut: Math.floor(Math.random() * 1000000),
packetsIn: Math.floor(Math.random() * 10000),
packetsOut: Math.floor(Math.random() * 10000),
}))
);
}
}
// controllers/metrics.controller.ts
import { Controller, Sse } from '@nestjs/common';
import { Observable, merge } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiSse } from '@nestjsvn/swagger-sse';
import { ApiTags } from '@nestjs/swagger';
import { MetricsService } from '../services/metrics.service';
@Controller('metrics')
@ApiTags('System Metrics')
export class MetricsController {
constructor(private metricsService: MetricsService) {}
@Sse('system')
@ApiSse({
summary: 'System metrics stream',
description: 'Real-time system performance metrics',
events: {
'cpu-metric': CpuMetricDto,
'memory-metric': MemoryMetricDto,
'network-metric': NetworkMetricDto,
},
})
streamSystemMetrics(): Observable<MessageEvent> {
const cpuEvents = this.metricsService.getCpuMetrics().pipe(
map((data) => ({
data: JSON.stringify(data),
type: 'cpu-metric',
} as MessageEvent))
);
const memoryEvents = this.metricsService.getMemoryMetrics().pipe(
map((data) => ({
data: JSON.stringify(data),
type: 'memory-metric',
} as MessageEvent))
);
const networkEvents = this.metricsService.getNetworkMetrics().pipe(
map((data) => ({
data: JSON.stringify(data),
type: 'network-metric',
} as MessageEvent))
);
return merge(cpuEvents, memoryEvents, networkEvents);
}
}
```
## E-commerce Order Tracking
Order status updates via SSE:
```typescript
// dto/order-events.dto.ts
export class OrderCreatedDto {
orderId: string;
customerId: string;
items: Array<{ productId: string; quantity: number; price: number }>;
total: number;
createdAt: Date;
}
export class OrderStatusUpdatedDto {
orderId: string;
customerId: string;
status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
updatedAt: Date;
estimatedDelivery?: Date;
}
export class PaymentProcessedDto {
orderId: string;
customerId: string;
amount: number;
paymentMethod: string;
processedAt: Date;
}
// services/order.service.ts
import { Injectable } from '@nestjs/common';
import { Subject, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Injectable()
export class OrderService {
private orderEvents = new Subject<any>();
createOrder(customerId: string, items: any[], total: number) {
const orderId = `order-${Date.now()}`;
this.orderEvents.next({
type: 'order-created',
data: {
orderId,
customerId,
items,
total,
createdAt: new Date(),
},
});
return orderId;
}
updateOrderStatus(orderId: string, customerId: string, status: string) {
this.orderEvents.next({
type: 'order-status-updated',
data: {
orderId,
customerId,
status,
updatedAt: new Date(),
estimatedDelivery: status === 'shipped' ? new Date(Date.now() + 3 * 24 * 60 * 60 * 1000) : undefined,
},
});
}
processPayment(orderId: string, customerId: string, amount: number, paymentMethod: string) {
this.orderEvents.next({
type: 'payment-processed',
data: {
orderId,
customerId,
amount,
paymentMethod,
processedAt: new Date(),
},
});
}
getOrderEventsForCustomer(customerId: string): Observable<MessageEvent> {
return this.orderEvents.pipe(
filter((event) => event.data.customerId === customerId),
map((event) => ({
data: JSON.stringify(event.data),
type: event.type,
} as MessageEvent))
);
}
}
// controllers/orders.controller.ts
import { Controller, Sse, Query, Post, Body } from '@nestjs/common';
import { Observable } from 'rxjs';
import { ApiSse } from '@nestjsvn/swagger-sse';
import { ApiQuery, ApiBody, ApiTags } from '@nestjs/swagger';
import { OrderService } from '../services/order.service';
@Controller('orders')
@ApiTags('Orders')
export class OrdersController {
constructor(private orderService: OrderService) {}
@Sse('customer/:customerId')
@ApiQuery({ name: 'customerId', description: 'Customer ID to track orders for' })
@ApiSse({
summary: 'Customer order tracking',
description: 'Real-time order updates for a specific customer',
events: {
'order-created': OrderCreatedDto,
'order-status-updated': OrderStatusUpdatedDto,
'payment-processed': PaymentProcessedDto,
},
})
streamOrderEvents(@Query('customerId') customerId: string): Observable<MessageEvent> {
return this.orderService.getOrderEventsForCustomer(customerId);
}
@Post('create')
@ApiBody({
schema: {
type: 'object',
properties: {
customerId: { type: 'string' },
items: { type: 'array', items: { type: 'object' } },
total: { type: 'number' },
},
},
})
createOrder(@Body() body: any) {
const orderId = this.orderService.createOrder(body.customerId, body.items, body.total);
return { orderId };
}
@Post('update-status')
@ApiBody({
schema: {
type: 'object',
properties: {
orderId: { type: 'string' },
customerId: { type: 'string' },
status: { type: 'string' },
},
},
})
updateOrderStatus(@Body() body: any) {
this.orderService.updateOrderStatus(body.orderId, body.customerId, body.status);
return { success: true };
}
}
```
These examples demonstrate various real-world use cases for `@nestjsvn/swagger-sse`, from simple notifications to complex multi-event systems. Each example includes proper TypeScript typing, error handling, and comprehensive Swagger documentation.