Skip to content

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.

text
Spring Boot Controller / DTO

      springdoc-openapi

        Swagger / OpenAPI

         API Codegen

         Business API

Annotation 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:

java
@RestController
@RequestMapping("/user")
@Tag(name = "user", description = "User management")
public class UserController {
}
text
/user/list

modulePath = user

userApi

Use 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:

java
@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:

java
@Operation(operationId = "listUsers", summary = "List users")
@GetMapping("/list")
public AjaxResult<List<UserDto>> listUsers(UserQuery query) {
    // ...
}

operationId is the primary source of generated method names:

text
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:

java
@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:

java
@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:

java
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:

java
@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:

text
Java Return Type

OpenAPI Schema

TypeScript Type

Business API Return Value

Normal business endpoints should use explicit DTOs and a standard response wrapper:

java
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:

ts
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:

java
@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, raw Map, 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:

java
@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:

java
@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 patternEffect on generated output
First-level path has no stable business meaningUnstable modulePath and business API filenames
Missing or duplicate operationIdUnpredictable method names or duplicate-name conflicts
Different Tags within one moduleUpstream modules may split and cause file conflicts
Returning Object or unconstrained MapTypeScript types degrade and business return values cannot be narrowed
Successful response has no explicit SchemaIt may become void and be mistaken for a download
Path parameter names do not match placeholdersRequest template parameters are generated incorrectly
Document path contains an unconfigured gateway prefixThe 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.