When you change a backend API (DTO, controller, or operationId), follow this workflow to keep the generated Dart client in sync.
If you change a NestJS DTO but forget to regenerate the Dart client, the mobile app will:
- Have stale types
- Fail at compile time (type mismatch)
- Fail at runtime (serialization error)
apps/api/src/modules/posts/dto/post-response.dto.ts:
export class PostResponseDto {
@ApiProperty()
id: number;
@ApiProperty()
title: string;
@ApiProperty()
body: string;
// NEW FIELD
@ApiProperty()
authorName: string; // ← Added
@ApiProperty()
createdAt: Date;
}Ensure your controller has:
@Get(':id')
@ApiOperation({ operationId: 'getPost' }) // ← MUST have operationId
@ApiStandardResponse(PostResponseDto) // ← MUST use @ApiStandardResponse
async getPost(@Param('id') id: string) {
// ...
}Why:
operationId→ clean Dart method name (getPost()notpostsControllerGetPostUsingGet())@ApiStandardResponse→ wraps DTO in envelope shape{ data, meta, requestId }
NestJS app expose Swagger khi isSwaggerEnabled true (luôn bật trong
development; ở env khác cần ENABLE_SWAGGER=true). Mount path bao gồm
API prefix (api mặc định, xem API_PREFIX):
| Resource | URL |
|---|---|
| Swagger UI | http://localhost:3000/api-docs |
| OpenAPI JSON | http://localhost:3000/api-docs/json |
# Khởi động backend
pnpm --filter @mobile-boilerplate/api dev
# Lấy spec (codegen pipeline dùng)
curl http://localhost:3000/api-docs/json > openapi.jsonNote: codegen pipeline (
pnpm codegen:api) KHÔNG cần server đang chạy — boot NestJS ở stub mode (SKIP_DB=true) quaapps/api/scripts/export-openapi.tsvà dump spec trực tiếp. URL bên trên chỉ để inspect/debug thủ công.
From repo root:
pnpm codegen:apiThis:
- Reads NestJS OpenAPI spec
- Generates
packages/api_client/lib/api_client.dart - Creates typed methods (e.g.,
getPost(id: int) → Future<PostResponseDto>) - Overwrites previous client — version control will show diff
Output:
✔ Generated api_client from openapi.json
✔ Updated packages/api_client/lib/
Mobile code now has compile errors (new fields, changed types):
Before:
final post = await apiClient.getPost(id: 1);
final author = post.author; // ERROR: field doesn't existAfter:
final post = await apiClient.getPost(id: 1);
final author = post.authorName; // ✓ OK (new field)Fix all compile errors in your feature code (repository, UI, tests).
E2E tests on backend:
// apps/api/test/posts.e2e-spec.ts
expect(response.body.data).toHaveProperty('authorName'); // ← NEW FIELDMobile tests (Provider, widget):
// test/features/posts/posts_repository_test.dart
final post = await repository.fetchPost(1);
expect(post.authorName, isNotEmpty); // ← Verify new field populatedgit add apps/api/src/modules/posts/dto/post-response.dto.ts
git add packages/api_client/
git add test/features/posts/
git commit -m "feat(posts): add author name to post response
- Add authorName field to PostResponseDto
- Regenerate Dart client
- Update mobile tests to verify new field"codegen-check.yml runs on every PR:
- Detects changes to
apps/api/src/**/*.dto.ts - Runs
pnpm codegen:api - Compares generated output to
packages/api_client/ - Fails PR if mismatch — ensures you regenerated
| Mistake | Symptom | Fix |
|---|---|---|
Forgot operationId in controller |
Generated method name is ugly (postsControllerGetPostUsingGet) |
Add @ApiOperation({ operationId: 'getPost' }) |
| Forgot to regenerate Dart client | Mobile compile errors or runtime crashes | Run pnpm codegen:api |
| Changed DTO but forgot to update tests | Tests pass but mobile breaks | Update e2e test expectations |
| Forgot to commit generated files | CI fails (codegen-check detects mismatch) | git add packages/api_client/ |
Used raw @ApiOkResponse instead of @ApiStandardResponse |
Envelope wrapper missing in spec | Use @ApiStandardResponse(Dto) |
Rename field (breaking change):
-
Backend:
// OLD export class PostResponseDto { content: string; // ← Was "content" } // NEW export class PostResponseDto { body: string; // ← Now "body" }
-
Regenerate:
pnpm codegen:api
-
Mobile:
// OLD final text = post.content; // ✗ COMPILE ERROR // NEW final text = post.body; // ✓ OK
-
Bump version:
feat!: rename post.content to post.body (BREAKING) -
Update changelog (semantic-release auto-does this).
- Design API first — define DTOs before implementing service
- Test OpenAPI spec — use Swagger UI to verify endpoint shape
- Generate early, generate often — don't batch changes; regenerate per feature
- Review diffs — check what changed in generated client (new fields, renamed methods)
- Use operationId consistently —
verbNounpattern (getPost, createPost, listPosts, updatePost, deletePost)
You do NOT regenerate when:
- Changing service logic (no DTO change)
- Adding validation (DTO unchanged)
- Fixing bugs in unrelated features
You DO regenerate when:
- Adding/removing/renaming DTO fields
- Changing field types
- Adding/removing controller routes
- Changing operationId
See Also: