Backend Conventions
This chapter uses Spring Boot and springdoc-openapi to illustrate how backend code can produce stable OpenAPI documents, allowing API Codegen to generate readable and predictable Business APIs.
Spring Boot Controller / DTO
↓
springdoc-openapi
↓
Swagger / OpenAPI
↓
API Codegen
↓
Business APIAnnotation syntax changes across Spring Boot and springdoc-openapi versions, but Codegen ultimately depends on paths, operationId, Tags, parameters, and response Schemas in the final OpenAPI document. Always check the actual generated document after backend changes.
Business Path Conventions
A Controller's first-level path should represent a stable business module:
@RestController
@RequestMapping("/user")
@Tag(name = "user", description = "User management")
public class UserController {
}/user/list
↓
modulePath = user
↓
userApiUse first-level paths with clear business meaning, such as /user, /order, and /role. Gateway, environment, and version prefixes should not be mixed arbitrarily into business modules. If document paths do include them, align them with Codegen's basePath and pathInDocument configuration.
Method Path Conventions
Paths within a module should describe stable resources or operations:
@GetMapping("/list")
@GetMapping("/{id}")
@PutMapping("/{id}/status")These become the business methods' requestPath values. Once frontend code uses a path, avoid renaming it solely because of implementation refactoring.
operationId
Every endpoint should provide a clear, stable, and as globally unique as possible operationId through @Operation:
@Operation(operationId = "listUsers", summary = "List users")
@GetMapping("/list")
public AjaxResult<List<UserDto>> listUsers(UserQuery query) {
// ...
}operationId is the primary source of generated method names:
operationId: listUsers
↓
userApi.listUsers()Do not rely on springdoc-openapi or an upstream generator to append numeric suffixes for duplicate names. Duplicate values trigger Codegen's duplicateMethodStrategy.
Tags
Use the same Tag throughout one business module and make it express the same ownership as the first-level business path:
@Tag(name = "user", description = "User management")
@RequestMapping("/user")Tags affect upstream module grouping in swagger-typescript-api, while final modulePath is still derived mainly from the normalized URL path. If Tags and paths disagree, endpoints from the same module may be split or file conflicts may occur.
Parameters
Path Parameters
Path placeholders and parameter names must match, with types declared explicitly:
@Operation(operationId = "getUser")
@GetMapping("/{id}")
public AjaxResult<UserDto> getUser(@PathVariable("id") Long id) {
// ...
}OpenAPI Path parameters become separate method arguments and are inserted into the request-path template.
Query Parameters
Use a clear query DTO for filters, pagination, and sorting, with stable field types, names, and required status:
public class UserQuery {
@Schema(description = "Name keyword")
private String keyword;
@Schema(description = "Page number", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer page;
}Query parameters generated by springdoc-openapi become the frontend query object. Verify that the actual document expands the DTO into in: query parameters.
Request Body
Use an explicit DTO for JSON request bodies rather than accepting Object or an unconstrained Map:
@Operation(operationId = "createUser")
@PostMapping
public AjaxResult<UserDto> createUser(@RequestBody UserInput input) {
// ...
}Request Body becomes the business method's data argument. Its required status and TypeScript type come from the final Schema.
Response Models
Response models directly determine generated types and Business API return values:
Java Return Type
↓
OpenAPI Schema
↓
TypeScript Type
↓
Business API Return ValueNormal business endpoints should use explicit DTOs and a standard response wrapper:
public class AjaxResult<T> {
private Integer code;
private String message;
private T data;
}Also ensure that springdoc-openapi expands generics in the actual document. If the project generates a wrapper Schema named AjaxResultUser and configures:
responseSchema: {
namePrefix: 'AjaxResult',
dataField: 'data',
}Codegen tries to narrow the return type to the User corresponding to the data field. If springdoc-openapi generates unstable Schema names, declare a stable name for the concrete response:
@Schema(name = "AjaxResultUser")
public class UserResult extends AjaxResult<UserDto> {
}Exact names and generic expansion depend on the project's springdoc-openapi version and configuration. Inspect the final /v3/api-docs, then align responseSchema.namePrefix with the actual Schema names.
Avoid:
- Returning
Object, rawMap, or a dynamic structure without a Schema. - Omitting the response body for an ordinary successful response.
- Keeping generics only in Java types when the generated document does not expand
data. - Declaring a Controller type that differs from the actual response structure.
When an upstream tool parses a successful response as void, the current Codegen template treats it as a download endpoint. Ordinary operations such as delete, enable, or disable should therefore declare an explicit response Schema.
Uploads and Downloads
Uploads
A Spring Boot upload endpoint should declare multipart/form-data and an explicit file field:
@Operation(operationId = "uploadAvatar")
@PostMapping(value = "/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public AjaxResult<FileInfoDto> uploadAvatar(
@RequestPart("file") MultipartFile file) {
// ...
}The final OpenAPI document should contain multipart/form-data, and the file field should be a binary string. Codegen does not currently switch to $http.upload() automatically. Compose or extend the generated API outside the generated directory when specialized upload behavior is required.
Downloads
A download endpoint should return binary content and set the media type and Content-Disposition correctly:
@Operation(operationId = "downloadReport")
@GetMapping(value = "/report", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<Resource> downloadReport() {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=report.csv")
.body(resource);
}Different OpenAPI declarations may produce different upstream parsing results. The current template generates $http.downloadFile() only when the successful response is parsed as void, so review generated download methods instead of assuming backend declarations will always trigger the conversion.
Commonly Discouraged Patterns
| Backend pattern | Effect on generated output |
|---|---|
| First-level path has no stable business meaning | Unstable modulePath and business API filenames |
Missing or duplicate operationId | Unpredictable method names or duplicate-name conflicts |
| Different Tags within one module | Upstream modules may split and cause file conflicts |
Returning Object or unconstrained Map | TypeScript types degrade and business return values cannot be narrowed |
| Successful response has no explicit Schema | It may become void and be mistaken for a download |
| Path parameter names do not match placeholders | Request template parameters are generated incorrectly |
| Document path contains an unconfigured gateway prefix | The wrong first-level path becomes modulePath |
The acceptance criterion for backend conventions is not the annotations themselves. It is whether the final OpenAPI document expresses endpoints consistently and allows the default generation rules to produce the expected code.