---
name: api-design
description: RESTful API design standards for GoodGo microservices. Use for new API endpoints, DTOs, controllers, OpenAPI documentation, or standardized responses.
compatibility: ".NET 10+, ASP.NET Core, MediatR, Swashbuckle, Asp.Versioning"
metadata:
  author: Velik Ho
  version: "2.0"
---

# RESTful API Design Standards / Tiêu Chuẩn Thiết Kế RESTful API

Standards for designing consistent, maintainable APIs in GoodGo microservices.

## When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:
- Creating new API endpoints / Tạo API endpoints mới
- Designing request/response DTOs / Thiết kế DTOs
- Implementing controllers with MediatR / Triển khai controllers với MediatR
- Writing OpenAPI/Swagger documentation / Viết tài liệu OpenAPI/Swagger
- Standardizing error responses / Chuẩn hóa error responses
- Implementing pagination, filtering / Triển khai pagination, filtering

## Core Concepts / Khái Niệm Cốt Lõi

### Clean Architecture Layers / Các Tầng Clean Architecture

```
src/
├── ServiceName.API/           # Controllers, DTOs, Middleware
│   ├── Controllers/           # API Controllers
│   └── Application/           # Commands, Queries, Handlers
├── ServiceName.Domain/        # Entities, Aggregates, Interfaces
└── ServiceName.Infrastructure/ # Repositories, External Services
```

### API Response Wrapper / Wrapper Response Chuẩn

```csharp
/// <summary>
/// EN: Standard API response wrapper.
/// VI: Wrapper response API chuẩn.
/// </summary>
public class ApiResponse<T>
{
    public bool Success { get; set; }
    public T? Data { get; set; }
    public string? Error { get; set; }
    public PaginationInfo? Pagination { get; set; }
}

public record PaginationInfo(
    int Page, 
    int Limit, 
    int Total, 
    int TotalPages);
```

### URL Structure / Cấu Trúc URL

```
/api/v{version}/{resource}/{id?}/{sub-resource?}

GET    /api/v1/files              # List files
POST   /api/v1/files              # Create file
GET    /api/v1/files/{id}         # Get file by ID
PUT    /api/v1/files/{id}         # Update file
DELETE /api/v1/files/{id}         # Delete file
GET    /api/v1/files/{id}/versions # Get file versions
```

## Key Patterns / Mẫu Chính

### Controller with MediatR / Controller với MediatR

```csharp
/// <summary>
/// EN: Controller for file operations.
/// VI: Controller cho các thao tác file.
/// </summary>
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/files")]
[SwaggerTag("File Management - Upload, download, and manage files")]
public class FilesController : ControllerBase
{
    private readonly IMediator _mediator;

    public FilesController(IMediator mediator)
    {
        _mediator = mediator;
    }

    /// <summary>
    /// EN: Get file by ID.
    /// VI: Lấy thông tin file theo ID.
    /// </summary>
    [HttpGet("{fileId:guid}")]
    [Authorize]
    [SwaggerOperation(Summary = "Get file by ID")]
    [SwaggerResponse(200, "File retrieved successfully")]
    [SwaggerResponse(404, "File not found")]
    public async Task<ActionResult<ApiResponse<FileDto>>> GetFile(
        Guid fileId,
        CancellationToken cancellationToken = default)
    {
        var userId = GetUserId();
        if (string.IsNullOrEmpty(userId))
            return Unauthorized(new ApiResponse<FileDto> 
            { 
                Success = false, 
                Error = "User ID not found" 
            });

        var query = new GetFileQuery(fileId, userId);
        var result = await _mediator.Send(query, cancellationToken);

        if (result == null)
            return NotFound(new ApiResponse<FileDto> 
            { 
                Success = false, 
                Error = "File not found" 
            });

        return Ok(new ApiResponse<FileDto> { Success = true, Data = result });
    }

    private string? GetUserId() => 
        User.FindFirstValue(ClaimTypes.NameIdentifier);
}
```

### DTO with Records / DTO với Records

```csharp
/// <summary>
/// EN: DTO for file information.
/// VI: DTO cho thông tin file.
/// </summary>
public record FileDto(
    Guid Id,
    string UserId,
    string FileName,
    string ContentType,
    long FileSizeBytes,
    string AccessLevel,
    DateTime UploadedAt);

/// <summary>
/// EN: Result for user files query with pagination.
/// VI: Kết quả query files với phân trang.
/// </summary>
public record UserFilesResult(
    IReadOnlyList<FileDto> Files,
    int TotalCount);

/// <summary>
/// EN: Mapper from domain entities to DTOs.
/// VI: Mapper từ domain entities sang DTOs.
/// </summary>
public static class FileDtoMapper
{
    public static FileDto ToDto(this StorageFile file) => new(
        file.Id,
        file.UserId,
        file.FileName,
        file.ContentType,
        file.FileSizeBytes,
        file.AccessLevel.ToString(),
        file.UploadedAt);
}
```

### Request Validation / Validation Request

```csharp
/// <summary>
/// EN: Command for file upload.
/// VI: Command để upload file.
/// </summary>
public record UploadFileCommand(
    Stream FileStream,
    string FileName,
    string ContentType,
    long FileSize,
    string UserId,
    Guid? FolderId,
    FileAccessLevel AccessLevel) : IRequest<UploadFileResult>;

/// <summary>
/// EN: Query for user files with pagination.
/// VI: Query lấy files với phân trang.
/// </summary>
public record GetUserFilesQuery(
    string UserId,
    int Skip = 0,
    int Take = 20,
    string? Search = null) : IRequest<UserFilesResult>;
```

### List Endpoint with Pagination / Endpoint List với Phân Trang

```csharp
[HttpGet]
[Authorize]
[SwaggerOperation(Summary = "Get user files")]
public async Task<ActionResult<ApiResponse<UserFilesResult>>> GetFiles(
    [FromQuery] int skip = 0,
    [FromQuery] int take = 20,
    [FromQuery] string? search = null,
    CancellationToken cancellationToken = default)
{
    var userId = GetUserId();
    if (string.IsNullOrEmpty(userId))
        return Unauthorized(new ApiResponse<UserFilesResult> 
        { 
            Success = false, 
            Error = "User ID not found" 
        });

    var query = new GetUserFilesQuery(userId, skip, take, search);
    var result = await _mediator.Send(query, cancellationToken);

    return Ok(new ApiResponse<UserFilesResult> 
    { 
        Success = true, 
        Data = result,
        Pagination = new PaginationInfo(
            Page: skip / take + 1,
            Limit: take,
            Total: result.TotalCount,
            TotalPages: (int)Math.Ceiling(result.TotalCount / (double)take))
    });
}
```

## Common Mistakes / Lỗi Thường Gặp

### 1. Inconsistent Response Format / Response Format Không Nhất Quán

```csharp
// ❌ BAD: Trả về trực tiếp data
return Ok(result);

// ✅ GOOD: Dùng ApiResponse wrapper
return Ok(new ApiResponse<FileDto> { Success = true, Data = result });
```

### 2. Missing API Versioning / Thiếu API Versioning

```csharp
// ❌ BAD: Không có versioning
[Route("api/files")]

// ✅ GOOD: Có API versioning
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/files")]
```

### 3. No Bilingual XML Comments / Thiếu Comment Song Ngữ

```csharp
// ❌ BAD: Chỉ tiếng Anh
/// <summary>Get file by ID.</summary>

// ✅ GOOD: Song ngữ EN/VI
/// <summary>
/// EN: Get file by ID.
/// VI: Lấy thông tin file theo ID.
/// </summary>
```

### 4. Missing Swagger Annotations / Thiếu Swagger Annotations

```csharp
// ❌ BAD: Không có Swagger annotations
[HttpGet("{fileId:guid}")]
public async Task<ActionResult<FileDto>> GetFile(Guid fileId)

// ✅ GOOD: Đầy đủ Swagger annotations
[HttpGet("{fileId:guid}")]
[SwaggerOperation(Summary = "Get file by ID")]
[SwaggerResponse(200, "File retrieved successfully")]
[SwaggerResponse(404, "File not found")]
public async Task<ActionResult<ApiResponse<FileDto>>> GetFile(Guid fileId)
```

### 5. Returning 200 for Errors / Trả 200 Cho Lỗi

```csharp
// ❌ BAD: 200 cho error
return Ok(new ApiResponse<FileDto> { Success = false, Error = "Not found" });

// ✅ GOOD: Đúng status code
return NotFound(new ApiResponse<FileDto> { Success = false, Error = "File not found" });
```

## Quick Reference / Tham Chiếu Nhanh

### HTTP Methods & Status Codes

| Method | Action | Success | Error Codes |
|--------|--------|---------|-------------|
| **GET** | Retrieve | 200 | 404 |
| **POST** | Create | 200/201 | 400, 409 |
| **PUT** | Full update | 200 | 400, 404 |
| **PATCH** | Partial update | 200 | 400, 404 |
| **DELETE** | Remove | 200/204 | 404 |

### Common Error Codes

| Code | Meaning | When to Use |
|------|---------|-------------|
| 400 | Bad Request | Validation errors |
| 401 | Unauthorized | Missing/invalid token |
| 403 | Forbidden | No permission |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate resource |
| 422 | Unprocessable | Business rule violation |
| 429 | Too Many Requests | Rate limited |

### Controller Attributes

| Attribute | Purpose |
|-----------|---------|
| `[ApiController]` | Enable API behaviors |
| `[ApiVersion("1.0")]` | API versioning |
| `[Authorize]` | Require authentication |
| `[SwaggerTag("Description")]` | Swagger grouping |
| `[SwaggerOperation]` | Endpoint documentation |
| `[SwaggerResponse]` | Response documentation |

### ApiResponse Format

```csharp
// Success response
{ "success": true, "data": {...}, "pagination": {...} }

// Error response
{ "success": false, "error": "Error message" }
```

## Resources / Tài Nguyên

- [API Versioning Strategy](../api-versioning-strategy/SKILL.md) - Versioning patterns
- [Error Handling Patterns](../error-handling-patterns/SKILL.md) - Error handling
- [Middleware Patterns](../middleware-patterns/SKILL.md) - Request handling
- [Project Rules](../project-rules/SKILL.md) - Coding standards
- [Skill Authoring](../skill-authoring/SKILL.md) - How to write skills
