--- name: api-design description: RESTful API design standards for GoodGo microservices. Use when creating new API endpoints, designing DTOs, implementing controllers, writing OpenAPI documentation, or standardizing API responses. --- # RESTful API Design Standards ## When to Use This Skill Use this skill when: - Creating new API endpoints - Designing request/response DTOs - Implementing controllers and routes - Writing OpenAPI/Swagger documentation - Standardizing error responses - Implementing pagination, filtering, and sorting - Setting up API versioning - Designing resource relationships ## Core Principles 1. **Consistency**: All APIs follow the same patterns 2. **Predictability**: Developers can guess endpoint behavior 3. **Simplicity**: Easy to understand and use 4. **Documentation**: Self-documenting through OpenAPI 5. **Error Handling**: Clear, actionable error messages ## Request/Response Flow The following diagram illustrates how a request flows through the API layers: ```mermaid sequenceDiagram participant Client participant Middleware participant Controller participant Service participant Repository participant Database Client->>Middleware: HTTP Request Middleware->>Middleware: Authentication
Rate Limiting
Validation Middleware->>Controller: Validated Request Controller->>Controller: Parse DTO
Extract Params Controller->>Service: Business Logic Call Service->>Repository: Data Access Call Repository->>Database: Query Execution Database-->>Repository: Data Result Repository-->>Service: Entity/Entities Service-->>Controller: Business Result Controller->>Controller: Transform to DTO
Format Response Controller-->>Middleware: Response Object Middleware-->>Client: HTTP Response ``` ## API Structure Hierarchy The following diagram shows the hierarchical structure of RESTful API endpoints: ```mermaid graph TD A[API Base URL
https://api.goodgo.com] --> B[Version
/v1] B --> C[Resource Collection
/users] B --> D[Resource Collection
/orders] B --> E[Resource Collection
/products] C --> F[Resource Instance
/users/:id] C --> G[Sub-Resource
/users/:id/orders] F --> H[GET /users/:id
Retrieve user] F --> I[PUT /users/:id
Update user] F --> J[DELETE /users/:id
Delete user] C --> K[GET /users
List users] C --> L[POST /users
Create user] G --> M[GET /users/:id/orders
List user orders] G --> N[POST /users/:id/orders
Create order] style A fill:#e1f5ff style B fill:#b3e5fc style C fill:#81d4fa style D fill:#81d4fa style E fill:#81d4fa style F fill:#4fc3f7 style G fill:#4fc3f7 ``` ## URL Structure ``` https://api.goodgo.com/v1/{resource}/{id}/{sub-resource} Examples: GET /v1/users # List users POST /v1/users # Create user GET /v1/users/123 # Get user by ID PUT /v1/users/123 # Update user DELETE /v1/users/123 # Delete user GET /v1/users/123/orders # Get user's orders POST /v1/users/123/orders # Create order for user ``` ## HTTP Methods - **GET**: Retrieve resource(s) - Safe, Idempotent - **POST**: Create new resource - Not idempotent - **PUT**: Full update - Idempotent - **PATCH**: Partial update - Idempotent - **DELETE**: Remove resource - Idempotent ## Standard Response Format ### Success Response ```typescript interface SuccessResponse { success: true; data: T; metadata?: { timestamp: string; version: string; requestId: string; }; pagination?: { page: number; limit: number; total: number; totalPages: number; }; } // Example { "success": true, "data": { "id": "123", "email": "user@example.com", "name": "John Doe" }, "metadata": { "timestamp": "2024-01-01T00:00:00Z", "version": "1.0.0", "requestId": "req_abc123" } } ``` ### Error Response ```typescript interface ErrorResponse { success: false; error: { code: string; message: string; details?: any; field?: string; stack?: string; // Only in development }; metadata?: { timestamp: string; requestId: string; }; } // Example { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Invalid email format", "field": "email", "details": { "provided": "invalid-email", "expected": "valid email address" } } } ``` ## Status Codes ```typescript // Success codes 200 OK // GET, PUT, PATCH success 201 Created // POST success with resource creation 204 No Content // DELETE success // Client errors 400 Bad Request // Invalid request data 401 Unauthorized // Missing/invalid authentication 403 Forbidden // Valid auth but no permission 404 Not Found // Resource doesn't exist 409 Conflict // Resource conflict (duplicate) 422 Unprocessable // Validation errors // Server errors 500 Internal Error // Unexpected server error 502 Bad Gateway // External service error 503 Service Unavailable // Service temporarily down 504 Gateway Timeout // External service timeout ``` ## DTOs (Data Transfer Objects) ### Request DTOs ```typescript // create.dto.ts import { IsEmail, IsNotEmpty, IsOptional, MinLength } from 'class-validator'; export class CreateUserDto { @IsEmail() @IsNotEmpty() email: string; @MinLength(6) @IsNotEmpty() password: string; @IsOptional() name?: string; } // update.dto.ts export class UpdateUserDto { @IsEmail() @IsOptional() email?: string; @IsOptional() name?: string; @IsOptional() avatar?: string; } // query.dto.ts export class QueryUsersDto { @IsOptional() @Type(() => Number) @Min(1) page?: number = 1; @IsOptional() @Type(() => Number) @Min(1) @Max(100) limit?: number = 10; @IsOptional() search?: string; @IsOptional() @IsIn(['createdAt', 'name', 'email']) sortBy?: string = 'createdAt'; @IsOptional() @IsIn(['asc', 'desc']) order?: 'asc' | 'desc' = 'desc'; } ``` ### Response DTOs ```typescript // user.response.dto.ts export class UserResponseDto { id: string; email: string; name: string; avatar?: string; role: string; createdAt: Date; updatedAt: Date; // Hide sensitive data static fromEntity(user: User): UserResponseDto { const { password, ...data } = user; return data; } } // paginated.response.dto.ts export class PaginatedResponseDto { data: T[]; pagination: { page: number; limit: number; total: number; totalPages: number; }; } ``` ## Controller Implementation ```typescript // user.controller.ts @Controller('users') @ApiTags('Users') export class UserController { constructor(private readonly userService: UserService) {} @Get() @ApiOperation({ summary: 'List users' }) @ApiQuery({ type: QueryUsersDto }) @ApiResponse({ status: 200, type: PaginatedResponseDto }) async list(@Query() query: QueryUsersDto): Promise { const { data, total } = await this.userService.findAll(query); return { success: true, data: data.map(UserResponseDto.fromEntity), pagination: { page: query.page, limit: query.limit, total, totalPages: Math.ceil(total / query.limit) } }; } @Get(':id') @ApiOperation({ summary: 'Get user by ID' }) @ApiParam({ name: 'id', type: 'string' }) @ApiResponse({ status: 200, type: UserResponseDto }) @ApiResponse({ status: 404, description: 'User not found' }) async getById(@Param('id') id: string): Promise { const user = await this.userService.findById(id); if (!user) { throw new HttpException( { success: false, error: { code: 'USER_NOT_FOUND', message: `User with ID ${id} not found` } }, HttpStatus.NOT_FOUND ); } return { success: true, data: UserResponseDto.fromEntity(user) }; } @Post() @ApiOperation({ summary: 'Create user' }) @ApiBody({ type: CreateUserDto }) @ApiResponse({ status: 201, type: UserResponseDto }) async create(@Body() dto: CreateUserDto): Promise { const user = await this.userService.create(dto); return { success: true, data: UserResponseDto.fromEntity(user) }; } @Put(':id') @ApiOperation({ summary: 'Update user' }) @UseGuards(AuthGuard) async update( @Param('id') id: string, @Body() dto: UpdateUserDto ): Promise { const user = await this.userService.update(id, dto); return { success: true, data: UserResponseDto.fromEntity(user) }; } @Delete(':id') @ApiOperation({ summary: 'Delete user' }) @UseGuards(AuthGuard, RolesGuard) @Roles('admin') async delete(@Param('id') id: string): Promise { await this.userService.delete(id); return { success: true, data: { deleted: true } }; } } ``` ## OpenAPI/Swagger Documentation ```yaml # openapi/user-service.yaml openapi: 3.0.0 info: title: User Service API version: 1.0.0 description: User management endpoints servers: - url: https://api.goodgo.com/v1 paths: /users: get: summary: List users parameters: - name: page in: query schema: type: integer default: 1 - name: limit in: query schema: type: integer default: 10 responses: '200': description: List of users content: application/json: schema: $ref: '#/components/schemas/UserListResponse' post: summary: Create user requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateUserRequest' responses: '201': description: User created '400': description: Validation error ``` ## Pagination Pattern ```typescript // pagination.service.ts export class PaginationService { paginate( query: any, options: { page: number; limit: number; sortBy?: string; order?: 'asc' | 'desc'; } ) { const skip = (options.page - 1) * options.limit; return { skip, take: options.limit, orderBy: options.sortBy ? { [options.sortBy]: options.order || 'desc' } : undefined }; } } ``` ## Error Handling The following diagram illustrates the error handling flow in the API: ```mermaid flowchart TD A[Request Received] --> B{Validation
Middleware} B -->|Invalid| C[ValidationError] B -->|Valid| D[Controller] D --> E{Business Logic} E -->|Not Found| F[NotFoundError] E -->|Unauthorized| G[UnauthorizedError] E -->|Forbidden| H[ForbiddenError] E -->|Conflict| I[ConflictError] E -->|Success| J[Return Success Response] E -->|Unexpected Error| K[Generic Error] C --> L[Error Handler
Middleware] F --> L G --> L H --> L I --> L K --> L L --> M{Error Type?} M -->|ValidationError| N[400 Bad Request
VALIDATION_ERROR] M -->|UnauthorizedError| O[401 Unauthorized
UNAUTHORIZED] M -->|ForbiddenError| P[403 Forbidden
FORBIDDEN] M -->|NotFoundError| Q[404 Not Found
NOT_FOUND] M -->|ConflictError| R[409 Conflict
CONFLICT] M -->|Unknown Error| S{Development
Mode?} S -->|Yes| T[500 Internal Error
INTERNAL_ERROR
+ Stack Trace] S -->|No| U[500 Internal Error
INTERNAL_ERROR
Generic Message] N --> V[Format Error Response
success: false
error: code, message, details] O --> V P --> V Q --> V R --> V T --> V U --> V V --> W[Log Error
Server-Side] W --> X[Send HTTP Response] J --> X style A fill:#e1f5ff style J fill:#c8e6c9 style L fill:#fff9c4 style V fill:#ffccbc style X fill:#e1f5ff ``` ### Error Handler Implementation ```typescript // error.middleware.ts export function errorHandler( err: Error, req: Request, res: Response, next: NextFunction ) { const isDev = process.env.NODE_ENV === 'development'; // Known errors if (err instanceof ValidationError) { return res.status(400).json({ success: false, error: { code: 'VALIDATION_ERROR', message: err.message, details: err.errors } }); } if (err instanceof UnauthorizedError) { return res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: 'Authentication required' } }); } // Unknown errors logger.error('Unhandled error:', err); res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: isDev ? err.message : 'Internal server error', stack: isDev ? err.stack : undefined } }); } ``` ## Best Practices 1. **Resource Naming** - Use plural nouns (`/users` not `/user`) - Use kebab-case for multi-word resources - Keep URLs as short as possible 2. **Versioning** - Include version in URL (`/v1/users`) - Maintain backward compatibility - Deprecate old versions gracefully 3. **Security** - Always use HTTPS - Implement rate limiting - Validate all inputs - Use proper authentication/authorization 4. **Performance** - Implement pagination for lists - Use field filtering when possible - Cache responses appropriately - Compress responses (gzip) 5. **Documentation** - Keep OpenAPI spec up to date - Include examples in documentation - Document error responses - Version your documentation