---
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<br/>Rate Limiting<br/>Validation
    Middleware->>Controller: Validated Request
    Controller->>Controller: Parse DTO<br/>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<br/>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<br/>https://api.goodgo.com] --> B[Version<br/>/v1]
    B --> C[Resource Collection<br/>/users]
    B --> D[Resource Collection<br/>/orders]
    B --> E[Resource Collection<br/>/products]
    
    C --> F[Resource Instance<br/>/users/:id]
    C --> G[Sub-Resource<br/>/users/:id/orders]
    
    F --> H[GET /users/:id<br/>Retrieve user]
    F --> I[PUT /users/:id<br/>Update user]
    F --> J[DELETE /users/:id<br/>Delete user]
    
    C --> K[GET /users<br/>List users]
    C --> L[POST /users<br/>Create user]
    
    G --> M[GET /users/:id/orders<br/>List user orders]
    G --> N[POST /users/:id/orders<br/>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<T> {
  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<T> {
  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<ResponseDto> {
    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<ResponseDto> {
    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<ResponseDto> {
    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<ResponseDto> {
    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<ResponseDto> {
    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<T>(
    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<br/>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<br/>Middleware]
    F --> L
    G --> L
    H --> L
    I --> L
    K --> L
    
    L --> M{Error Type?}
    M -->|ValidationError| N[400 Bad Request<br/>VALIDATION_ERROR]
    M -->|UnauthorizedError| O[401 Unauthorized<br/>UNAUTHORIZED]
    M -->|ForbiddenError| P[403 Forbidden<br/>FORBIDDEN]
    M -->|NotFoundError| Q[404 Not Found<br/>NOT_FOUND]
    M -->|ConflictError| R[409 Conflict<br/>CONFLICT]
    M -->|Unknown Error| S{Development<br/>Mode?}
    
    S -->|Yes| T[500 Internal Error<br/>INTERNAL_ERROR<br/>+ Stack Trace]
    S -->|No| U[500 Internal Error<br/>INTERNAL_ERROR<br/>Generic Message]
    
    N --> V[Format Error Response<br/>success: false<br/>error: code, message, details]
    O --> V
    P --> V
    Q --> V
    R --> V
    T --> V
    U --> V
    
    V --> W[Log Error<br/>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