cherry-studio/packages/shared/data/api/api-design-guidelines.md
fullex e4ec7bba7c docs(api): add PATCH vs Dedicated Endpoints section to API design guidelines
- Introduced a decision tree to help determine when to use PATCH versus dedicated endpoints based on operation characteristics.
- Added guidelines for naming dedicated endpoints and provided examples for various scenarios, enhancing clarity on API design practices.
2025-12-27 11:17:47 +08:00

6.4 KiB

API Design Guidelines

Guidelines for designing RESTful APIs in the Cherry Studio Data API system.

Path Naming

Rule Example Notes
Use plural nouns for collections /topics, /messages Resources are collections
Use kebab-case for multi-word paths /user-settings Not camelCase or snake_case
Express hierarchy via nesting /topics/:topicId/messages Parent-child relationships
Avoid verbs for CRUD operations /topics not /getTopics HTTP methods express action

HTTP Method Semantics

Method Purpose Idempotent Typical Response
GET Retrieve resource(s) Yes 200 + data
POST Create resource No 201 + created entity
PUT Replace entire resource Yes 200 + updated entity
PATCH Partial update Yes 200 + updated entity
DELETE Remove resource Yes 204 / void

Standard Endpoint Patterns

// Collection operations
'/topics': {
  GET: { ... }   // List with pagination/filtering
  POST: { ... }  // Create new resource
}

// Individual resource operations
'/topics/:id': {
  GET: { ... }    // Get single resource
  PUT: { ... }    // Replace resource
  PATCH: { ... }  // Partial update
  DELETE: { ... } // Remove resource
}

// Nested resources (use for parent-child relationships)
'/topics/:topicId/messages': {
  GET: { ... }   // List messages under topic
  POST: { ... }  // Create message in topic
}

PATCH vs Dedicated Endpoints

Decision Criteria

Use this decision tree to determine the appropriate approach:

Operation characteristics:
├── Simple field update with no side effects?
│   └── Yes → Use PATCH
├── High-frequency operation with clear business meaning?
│   └── Yes → Use dedicated endpoint (noun-based sub-resource)
├── Operation triggers complex side effects or validation?
│   └── Yes → Use dedicated endpoint
├── Operation creates new resources?
│   └── Yes → Use POST to dedicated endpoint
└── Default → Use PATCH

Guidelines

Scenario Approach Example
Simple field update PATCH PATCH /messages/:id { data: {...} }
High-frequency + business meaning Dedicated sub-resource PUT /topics/:id/active-node { nodeId }
Complex validation/side effects Dedicated endpoint POST /messages/:id/move { newParentId }
Creates new resources POST dedicated POST /messages/:id/duplicate

Naming for Dedicated Endpoints

  • Prefer noun-based paths over verb-based when possible
  • Treat the operation target as a sub-resource: /topics/:id/active-node not /topics/:id/switch-branch
  • Use POST for actions that create resources or have non-idempotent side effects
  • Use PUT for setting/replacing a sub-resource value

Examples

// ✅ Good: Noun-based sub-resource for high-frequency operation
PUT /topics/:id/active-node
{ nodeId: string }

// ✅ Good: Simple field update via PATCH
PATCH /messages/:id
{ data: MessageData }

// ✅ Good: POST for resource creation
POST /messages/:id/duplicate
{ includeDescendants?: boolean }

// ❌ Avoid: Verb in path when noun works
POST /topics/:id/switch-branch  // Use PUT /topics/:id/active-node instead

// ❌ Avoid: Dedicated endpoint for simple updates
POST /messages/:id/update-content  // Use PATCH /messages/:id instead

Non-CRUD Operations

Use verb-based paths for operations that don't fit CRUD semantics:

// Search
'/topics/search': {
  GET: { query: { q: string } }
}

// Statistics / Aggregations
'/topics/stats': {
  GET: { response: { total: number, ... } }
}

// Resource actions (state changes, triggers)
'/topics/:id/archive': {
  POST: { response: { archived: boolean } }
}

'/topics/:id/duplicate': {
  POST: { response: Topic }
}

Query Parameters

Purpose Pattern Example
Pagination page + limit ?page=1&limit=20
Sorting orderBy + order ?orderBy=createdAt&order=desc
Filtering direct field names ?status=active&type=chat
Search q or search ?q=keyword

Response Status Codes

Use standard HTTP status codes consistently:

Status Usage Example
200 OK Successful GET/PUT/PATCH Return updated resource
201 Created Successful POST Return created resource
204 No Content Successful DELETE No body
400 Bad Request Invalid request format Malformed JSON
401 Unauthorized Authentication required Missing/invalid token
403 Forbidden Permission denied Insufficient access
404 Not Found Resource not found Invalid ID
409 Conflict Concurrent modification Version conflict
422 Unprocessable Validation failed Invalid field values
429 Too Many Requests Rate limit exceeded Throttling
500 Internal Error Server error Unexpected failure

Error Response Format

All error responses follow the DataApiError structure:

interface DataApiError {
  code: string      // ErrorCode enum value (e.g., 'NOT_FOUND')
  message: string   // Human-readable error message
  status: number    // HTTP status code
  details?: any     // Additional context (e.g., field errors)
  stack?: string    // Stack trace (development only)
}

Examples:

// 404 Not Found
{
  code: 'NOT_FOUND',
  message: "Topic with id 'abc123' not found",
  status: 404,
  details: { resource: 'Topic', id: 'abc123' }
}

// 422 Validation Error
{
  code: 'VALIDATION_ERROR',
  message: 'Request validation failed',
  status: 422,
  details: {
    fieldErrors: {
      name: ['Name is required', 'Name must be at least 3 characters'],
      email: ['Invalid email format']
    }
  }
}

Use DataApiErrorFactory utilities to create consistent errors:

import { DataApiErrorFactory } from '@shared/data/api'

throw DataApiErrorFactory.notFound('Topic', id)
throw DataApiErrorFactory.validation({ name: ['Required'] })
throw DataApiErrorFactory.database(error, 'insert topic')

Naming Conventions Summary

Element Case Example
Paths kebab-case, plural /user-settings, /topics
Path params camelCase :topicId, :messageId
Query params camelCase orderBy, pageSize
Body fields camelCase createdAt, userName
Error codes SCREAMING_SNAKE NOT_FOUND, VALIDATION_ERROR