OpenAPI
Import one module and your REST API documents itself. OpenApiModule serves an
OpenAPI 3.1 document at GET /api-json and a bundled, offline Swagger UI
at GET /api — composed from the route table and your Json<T> types. There is
no spec to hand-write, and it cannot drift from the code: change a handler,
the document changes with it.
Wire it in
Section titled “Wire it in”Add OpenApiModule::for_root to the app root, alongside the controllers it
should document:
use nestrs_openapi::OpenApiModule;
#[module( imports = [ UsersHttpModule, // ... the rest of your HTTP modules ... OpenApiModule::for_root(None), // None → reads NESTRS_OPENAPI__* / defaults ],)]pub struct AppModule;That’s the whole opt-in. The module self-mounts both endpoints on the existing
HttpTransport — same port, same CORS, no second server.
What you get
Section titled “What you get”GET /api-json— the OpenAPI 3.1 document, composed from the route table your app serves. Every#[controller]your app mounts contributes its operations; nothing is listed by hand.GET /api— a bundled Swagger UI. The assets ship inside the binary, so it works with no internet access and no CDN.- Schemas for free. Request and response bodies are derived from your
Json<T>payload types viaschemars. An entity declared with#[expose]already produces its JSON Schema — the same type feeds the handler, the GraphQL schema, and this document, so the three stay in sync by construction. See Database.
Run it
Section titled “Run it”$ curl -s http://localhost:3000/api-json | jq '.openapi, .info.title, (.paths | keys)'"3.1.0""nestrs API"[ "/users", "/users/{id}"]Open http://localhost:3000/api for the interactive
Swagger UI — try a request straight from the browser.
Enrich an operation with #[api]
Section titled “Enrich an operation with #[api]”The document is complete without annotations, but #[api(...)] adds a summary,
a longer description, and tags to any handler:
#[post("/")]#[api( summary = "Create a user in the caller's org", description = "Requires a bearer JWT. The user's org is taken from the \ caller's token, never the body.", tags("User"))]async fn create( &self, _authz: Authorize<Create, UserEntity>, auth: Ctx<Claims>, body: Valid<Json<CreateUserInput>>,) -> Result<Json<User>> { Ok(Json(self.svc.create_in_org(body.into_inner(), auth.org_id).await?))}summary and description show on the operation; tags(...) group operations
in the UI. Everything else — path, method, parameters, request and response
schemas, status codes — is inferred from the handler signature.
Configure the document
Section titled “Configure the document”The info block (title, version, description) comes from NESTRS_OPENAPI__* in
the .env cascade, with sensible defaults:
$ NESTRS_OPENAPI__TITLE="Acme API" \ NESTRS_OPENAPI__VERSION="2.1.0" \ NESTRS_OPENAPI__DESCRIPTION="Public REST surface" \ just dev apiOr pass an OpenApiConfig at the import site instead of reading the
environment:
use nestrs_openapi::{OpenApiConfig, OpenApiModule};
OpenApiModule::for_root(OpenApiConfig { title: "Acme API".into(), version: "2.1.0".into(), description: Some("Public REST surface".into()),})Going further
Section titled “Going further”- HTTP — the controllers, routes and
Json<T>types this document is built from. - Database —
#[expose]turns one entity into a wire DTO, a GraphQL type and a JSON Schema at once. - Security — bind
AuthGuard/AbilityGuard; protected routes still appear in the spec.
Reference
Section titled “Reference”apps/api/— mountsOpenApiModule::for_root(None)next to REST + GraphQL.crates/features/src/users/http/controller.rs— real#[api(...)]usage.crates/nestrs-openapi/—OpenApiModule,OpenApiConfig, the document composer, the bundled Swagger UI.