--- trigger: always_on --- # 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 ## 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 ## URL Structure ``` https://api.goodgo.com/v1/{resource}/{id}/{sub-resource} 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 ``` ## 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 ```typescript // Success interface SuccessResponse { success: true; data: T; pagination?: { page: number; limit: number; total: number; totalPages: number }; } // Error interface ErrorResponse { success: false; error: { code: string; message: string; details?: any; field?: string }; } ``` ## Key Patterns ### Request DTO ```typescript export class CreateUserDto { @IsEmail() @IsNotEmpty() email: string; @MinLength(6) @IsNotEmpty() password: string; @IsOptional() name?: string; } export class QueryUsersDto { @IsOptional() @Min(1) page?: number = 1; @IsOptional() @Min(1) @Max(100) limit?: number = 10; @IsOptional() search?: string; @IsOptional() @IsIn(['createdAt', 'name']) sortBy?: string = 'createdAt'; @IsOptional() @IsIn(['asc', 'desc']) order?: 'asc' | 'desc' = 'desc'; } ``` ### Controller ```typescript @Get() async list(@Query() query: QueryUsersDto) { 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') async getById(@Param('id') id: string) { const user = await this.userService.findById(id); if (!user) throw new NotFoundException({ success: false, error: { code: 'NOT_FOUND', message: 'User not found' } }); return { success: true, data: UserResponseDto.fromEntity(user) }; } ``` ## Best Practices - **Resource Naming**: Use plural nouns (`/users`), kebab-case for multi-word - **Versioning**: Include version in URL (`/v1/users`), maintain backward compatibility - **Security**: Use HTTPS, implement rate limiting, validate all inputs - **Performance**: Implement pagination, use field filtering, cache responses - **Documentation**: Keep OpenAPI spec up to date, include examples ## Common Mistakes 1. **Using Verbs in URLs**: Non-RESTful endpoints ``` # BAD: POST /api/v1/createUser # GOOD: POST /api/v1/users ``` 2. **Inconsistent Response Format**: Different structures for different endpoints ```typescript // BAD: res.json({ user: data }) vs res.json({ result: data }) // GOOD: res.json({ success: true, data }) ``` 3. **Wrong HTTP Status Codes**: Using 200 for errors ```typescript // BAD: res.status(200).json({ error: 'Not found' }); // GOOD: res.status(404).json({ success: false, error: { code: 'NOT_FOUND' } }); ``` 4. **Missing Pagination**: Returning all records ```typescript // BAD: prisma.user.findMany() // GOOD: prisma.user.findMany({ skip: (page - 1) * limit, take: limit }) ``` ## Quick Reference | HTTP Method | Action | Idempotent | Status Codes | |-------------|--------|------------|--------------| | **GET** | Retrieve | Yes | 200, 404 | | **POST** | Create | No | 201, 400, 409 | | **PUT** | Full update | Yes | 200, 400, 404 | | **PATCH** | Partial update | Yes | 200, 400, 404 | | **DELETE** | Remove | Yes | 204, 404 | **Response Format:** ```typescript // Success: { success: true, data: T, pagination?: {...} } // Error: { success: false, error: { code: string, message: string } } ``` **Common Error Codes:** - `400` - Bad Request (validation) - `401` - Unauthorized (no token) - `403` - Forbidden (no permission) - `404` - Not Found - `409` - Conflict (duplicate) - `422` - Unprocessable (business rule) - `429` - Rate limited ## Resources - [OpenAPI Specification](https://spec.openapis.org/oas/latest.html) - Official OpenAPI docs - [REST API Design](https://restfulapi.net/) - REST best practices - [Detailed Code Examples](./references/REFERENCE.md) - [API Versioning Strategy](../api-versioning-strategy/SKILL.md) - Versioning patterns - [API Gateway Advanced](../api-gateway-advanced/SKILL.md) - Gateway patterns - [Middleware Patterns](../middleware-patterns/SKILL.md) - Request handling