feat(api): upgrade libnest to v9 and document request bodies from their Zod schemas - #1596
Merged
joshunrau merged 7 commits intoOct 1, 2026
Merged
Conversation
Rename the inferred type of every schema an API route parses as a request body to the schema's own $-prefixed name (CreateGroupData -> $CreateGroupData, and so on), and update every consumer. One identifier that is both a type and a value is what lets a controller type a @Body() parameter as the schema itself, which libnest v9 needs to validate and document it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PATCH /v1/assignments/:id only lets a client cancel an assignment; the narrowing to status 'CANCELED' lived in an apps/api DTO. Move it here as $CancelAssignmentData so the controller can type its body as a schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libnest 9 replaces @nestjs/swagger with a Zod-native OpenAPI generator, so the app no longer depends on it directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libnest 9 generates the OpenAPI document from the Zod schemas that controller parameters are typed with, and documents any other type as an empty schema. Replace all 18 @ValidationSchema DTO classes with bodies typed as their schemas, so every request body is both validated and documented from one source. Import ApiOperation from libnest and drop @apitags: operations are now tagged by controller name, so the hand-written tag list in main.ts goes too. Paths in the document now carry the /v1 prefix the routes are served at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OpenAPI document is now built after versioning, so its paths carry the /v1 prefix. Fail on any request body documented as an empty schema, which is what a body typed as a class rather than a Zod schema produces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Load the Redoc page the API serves and check that a request body's fields render from its Zod schema. The page is reached on the API's own origin through a new apiURL, because its spec URL is absolute and the web origin's /api proxy does not route it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the DTO-class pattern in the api AGENTS.md, the add-endpoint playbook and the libnest notes with bodies typed as their Zod schemas, and stop crediting the NestJS Swagger module in the API reference page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Upgrades
apps/apito@douglasneuroinformatics/libnestv9, which replaces@nestjs/swaggerwith a Zod-native OpenAPI generator, and removes@nestjs/swaggerfrom the app.libnest 9 documents a request body from the Zod schema its
@Body()parameter is typed with. Anything else, including the old@ValidationSchemaDTO classes, is documented as an empty schema. So:@Body() data: $CreateGroupData), which drives both validation and the docs.packages/schemasnow share their schema's$-prefixed name (CreateGroupData→$CreateGroupData). Every consumer in api, web, demo and testing is updated. This is the documented "type and value at the same call site" variant frompackages/schemas/AGENTS.md.PATCH /v1/assignments/:idonly acceptsstatus: 'CANCELED'. That narrowing moves from a DTO into$CancelAssignmentData.ApiOperationnow comes from libnest, and@ApiTags/@ApiPropertyare removed. Operations are tagged by controller name, so the hand-writtentagslist inmain.tsis gone./spec.jsonnow carry the/v1prefix the routes are served at.Verification
pnpm lintandpnpm testpass (1553 tests).pnpm test:e2epassed 253/254. The one failure,subject-detail.spec.ts"should plot a selected measure on the graph tab", is on a code path this PR doesn't touch, and it passed 6/6 when re-run alone (3× chromium, 3× firefox).New tests:
apps/api/test/suites/01-boot.suite.tschecks that docs paths are versioned, that no request body is documented as an empty schema, and thatPOST /v1/groupshas its fields.testing/src/specs/api-docs.spec.tsrenders the docs page in Chromium and checks the create-group body's fields.Known issue (pre-existing, not addressed here)
Opened through the web origin's
/api/proxy (and Caddy's in production), the docs page shows "Document must be JSON object, got string". libnest's HTML uses an absolutespec-url="/spec.json", which resolves to the SPA rather than the API. libnest 8.4.1 built the URL the same way, so this is not a regression; the fix belongs in libnest (a relative spec URL). The e2e spec reaches the page on the API's own origin for that reason.Not carried over
The free-text
@ApiPropertydescriptions and examples are not in the generated docs. They could be restored with.describe()/.meta()on the schemas in a follow-up.🤖 Generated with Claude Code