4.6 KiB
api.dsl Conventions
Grammar and authoring conventions for domain/*.api.dsl files.
See domain/toir.api.dsl as the canonical example.
File location and naming
- All api.dsl files live in
domain/. - Naming:
<project>.api.dsl(e.g.toir.api.dsl). - Multiple api.dsl files are allowed but not required.
Two top-level block types
An api.dsl file contains two types of top-level blocks:
dto— defines the shape of a data transfer objectapi— declares a group of API endpoints for one resource
dto block
dto DTO.<Name> {
description "Human-readable description";
attribute <fieldName> {
description "Field description";
type <type>;
is required; // or: is nullable;
map <Entity>.<field>; // links to a field in domain/*.api.dsl
}
}
DTO naming convention
| DTO name | Purpose |
|---|---|
DTO.<Entity> |
Full response shape (GET by id, list items) |
DTO.<Entity>Create |
Create request body |
DTO.<Entity>Update |
Update request body (partial — all fields nullable) |
DTO.<Entity>ListRequest |
Paginated list request (filters + page) |
DTO.<Entity>ListResponse |
Paginated list response (content + page info) |
Types
Same scalar types as the domain DSL:
uuid, string, text, integer, decimal, date, boolean, or an enum name from
domain/*.api.dsl.
Cross-DTO references: DTO.<Name> or DTO.<Name>[].
Standard pagination types (not entity-specific; treated as well-known):
DTO.Filter[], DTO.PageRequest, DTO.PageInfo.
is required vs is nullable
Unlike the domain DSL (where these drive DB constraints), in api.dsl these drive the TypeScript DTO property modifier:
is required→field!: typein the generated classis nullable→field?: typein the generated class- Neither modifier → treated as optional (
field?: type)
map directive
map Entity.field links the DTO attribute to a domain entity field.
Rules:
- The entity and field must exist in
domain/*.api.dsl. - The scalar type must match the domain DSL field type (nullability may differ).
- Omit
maponly for pagination helper types (DTO.Filter[],DTO.PageRequest, etc.) that have no direct entity field counterpart.
Conflict resolution
If the type declared in a dto attribute conflicts with the domain DSL field type,
the domain DSL is correct. Fix the api.dsl.
api block
api API.<Name> {
description "API group description";
endpoint <endpointName> {
label "METHOD /path";
description "Endpoint description";
// For endpoints with a request body:
attribute request {
type DTO.<RequestDto>;
}
// For endpoints with a response body:
attribute response {
type DTO.<ResponseDto>;
}
// For path parameters:
attribute <paramName> {
type <type>;
}
}
}
api block naming
API.<EntityName> (e.g. API.Equipment, API.RepairOrder)
endpoint label
label "METHOD /path" is the authoritative declaration of HTTP verb and route.
| Label pattern | NestJS decorator | Notes |
|---|---|---|
"GET /resource/{id}" |
@Get(':id') |
|
"POST /resource" |
@Post() |
Create |
"POST /resource/page" |
@Post('page') |
Paginated list (body-based filter) |
"PUT /resource/{id}" |
@Put(':id') |
Full or partial update |
"DELETE /resource/{id}" |
@Delete(':id') |
Do not infer HTTP verbs from endpoint names. Always read the label.
Path parameters
Declared as a plain attribute inside the endpoint block (not wrapped in request
or response):
attribute id {
type uuid;
}
Standard 5-endpoint CRUD pattern
| Endpoint name | Label | Body |
|---|---|---|
list<Entity>s |
"POST /<path>/page" |
request: DTO.<Entity>ListRequest, response: DTO.<Entity>ListResponse |
get<Entity> |
"GET /<path>/{id}" |
path param id, response: DTO.<Entity> |
create<Entity> |
"POST /<path>" |
request: DTO.<Entity>Create |
update<Entity> |
"PUT /<path>/{id}" |
path param id, request: DTO.<Entity>Update |
delete<Entity> |
"DELETE /<path>/{id}" |
path param id |
Constraints
- Every
map Entity.fieldmust resolve to an existing entity + field indomain/*.api.dsl. - Types in api.dsl must be compatible with domain DSL field types (same scalar type).
- api.dsl must not define entities or enums. Those belong in
domain/*.api.dsl. - An api.dsl may omit domain entity fields from a DTO (e.g. no PK in Create DTO). It must not add fields that don't exist in the domain model.