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