api-conventions
GitHub定义 MX Space API 设计规范,涵盖控制器装饰器、认证、响应转换、分页及参数校验等标准。指导开发者编写符合规范的 HTTP 接口与控制器代码。
Trigger Scenarios
Install
npx skills add mx-space/core --skill api-conventions -g -y
SKILL.md
Frontmatter
{
"name": "api-conventions",
"description": "MX Space API design conventions. Apply when writing controllers, API endpoints, or handling HTTP requests.",
"user-invocable": false
}
MX Space API Design Conventions
Controller Decorators
// Use @ApiController instead of @Controller
// Dev environment has no prefix, production auto-adds /api/v{version} prefix
@ApiController('posts') // ✓
@Controller('posts') // ✗
Authentication
// Endpoints requiring login
@Auth()
async create() {}
// Optional auth (get current user status)
async get(@IsAuthenticated() isAuth: boolean) {}
// Get current user
async get(@CurrentUser() user: UserModel) {}
Response Transformation
ResponseInterceptor (global APP_INTERCEPTOR) wraps every controller return value:
| Return value | Emitted |
|---|---|
bare value T |
{ data: T } |
withMeta(data, meta) |
{ data, meta } |
undefined |
204 No Content |
@HTTPDecorators.RawResponse |
untouched — skips envelope and case conversion |
withMeta (from ~/common/response/envelope.types) is detected by an internal Symbol,
not by the presence of a data key — returning an object literal whose top-level keys
include data gets double-wrapped. CI enforces this via
scripts/check-controller-response-envelope.ts.
transformResponseCase (~/common/response/case-transform.ts) converts the response
data/meta to snake_case at the wire boundary:
createdAt→created_atcategoryId→category_id
Opt a field subtree out with @BypassCaseTransform(['items[].rawPayload']).
Pagination
Pagination belongs in meta, never merged into data. Build it with MetaObjectBuilder:
@Get('/')
async list(@Query() query: PagerDto) {
const result = await this.postRepository.list({
page: query.page,
size: query.size,
sortBy: query.sortBy,
sortOrder: query.sortOrder,
})
const metaBuilder = new MetaObjectBuilder().view('card').pagination({
page: result.pagination.currentPage,
size: result.pagination.size,
total: result.pagination.total,
totalPages: result.pagination.totalPage,
})
return withMeta(result.data, metaBuilder.build())
}
For CRUD boilerplate, use BasePgCrudFactory:
@ApiController(paths)
export class LinkControllerCrud extends BasePgCrudFactory({
repository: LinkRepository,
}) {
@Get('/')
async gets(@Query() pager: PagerDto) {
const { size = 10, page = 1 } = pager
return this.repository.list(page, size)
}
}
Parameter Validation
// Path parameters — use EntityIdDto for Snowflake entity IDs
@Get('/:id')
async get(@Param() params: EntityIdDto) {
return this.service.findById(params.id)
}
// For integer IDs or entity IDs (e.g. notes with nid)
@Get('/:id')
async get(@Param() params: IntIdOrEntityIdDto) {}
// Query parameters
@Get('/')
async list(@Query() query: PagerDto) {}
// Request body
@Post('/')
async create(@Body() body: CreateDto) {}
HTTP Methods
| Method | Purpose | Status Code |
|---|---|---|
| GET | Retrieve resource | 200 |
| POST | Create resource | 201 |
| PUT | Full update | 200 |
| PATCH | Partial update | 200 |
| DELETE | Delete resource | 204 |
Error Handling
import { BusinessException } from '~/common/exceptions/biz.exception'
import { ErrorCodeEnum } from '~/constants/error-code.constant'
// Business errors
throw new BusinessException(ErrorCodeEnum.PostNotFound)
throw new BusinessException(ErrorCodeEnum.SlugNotAvailable, slug)
// HTTP errors
throw new BadRequestException('Invalid input')
throw new NotFoundException('Resource not found')
throw new UnauthorizedException('Not logged in')
Idempotency
// Add idempotency protection for create operations
@Post('/')
@HTTPDecorators.Idempotence()
async create() {}
// Custom idempotency key
@HTTPDecorators.Idempotence({ key: 'custom-key' })
Caching
// Disable cache
@Get('/')
@HttpCache.disable
async list() {}
// Custom cache
@HttpCache({ ttl: 60, key: 'my-key' })
async get() {}
Version History
-
c2ffb56
Current 2026-08-19 23:54
更新响应转换机制以匹配信封/元数据响应模式,精简 AI 引导提示词,并强化翻译系统提示词。
- a28bdf5 2026-07-25 05:37


