> ## Documentation Index
> Fetch the complete documentation index at: https://nestcrate.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate OpenAPI docs for your nestrs API

> nestrs-openapi serves an OpenAPI 3.1 JSON document and Swagger UI for all routes registered via #[routes]. Enable it with one method call on NestApplication.

`nestrs-openapi` discovers all routes registered through `#[routes]` and `impl_routes!`, builds an OpenAPI 3.1 document, and serves it at `GET /openapi.json`. A Swagger UI page is available at `GET /docs`. You enable the whole thing with a single method call on `NestApplication` — no separate server, no build step.

## What is generated automatically

| Area | Behavior |
| - | - |
| Paths and HTTP methods | Auto-discovered from the `RouteRegistry` |
| `operationId` | Module path + handler function name |
| `summary` | Humanized handler name; override with `#[openapi(summary = "...")]` |
| `tags` | Inferred from the first path segment; override with `#[openapi(tag = "...")]` |
| `responses` | Default `200 OK`; extend with `#[openapi(responses = ((404, "..."), ...))]` |
| Request/response schemas | Via **`#[dto]`** types — they derive `schemars::JsonSchema`; land them under `components.schemas` with `schema_entry` / `with_schemas` (not auto-linked to individual operations) |

## Step-by-step setup

<Steps>
  <Step title="Enable the feature">
    Add the `openapi` feature to your `nestrs` dependency, or add `nestrs-openapi` directly if you need the standalone router.

    ```toml theme={null}
    [dependencies]
    nestrs = { version = "1.5.0", features = ["openapi"] }
    tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
    ```
  </Step>

  <Step title="Call enable_openapi() before listen">
    Chain `enable_openapi()` on the `NestApplication` returned by `NestFactory::create`. This registers `GET /openapi.json` and `GET /docs` on the same router as your API.

    ```rust theme={null}
    use nestrs::prelude::*;

    #[module]
    struct AppModule;

    #[tokio::main]
    async fn main() {
        NestFactory::create::<AppModule>()
            .enable_openapi()
            .listen(3000)
            .await;
    }
    ```

    Your API is now self-documenting. Open `http://localhost:3000/docs` in a browser to see the Swagger UI.
  </Step>

  <Step title="Annotate routes with #[openapi]">
    Enrich individual handlers with a custom summary, tag, or response codes. Handlers without annotations get sensible defaults.

    ```rust theme={null}
    #[controller(prefix = "/users", version = "v1")]
    pub struct UserController;

    #[routes(state = UserService)]
    impl UserController {
        #[get("/")]
        #[openapi(summary = "List all users", tag = "users")]
        pub async fn list(
            State(s): State<Arc<UserService>>,
        ) -> Result<Json<Vec<UserRow>>, HttpException> {
            s.list_users().await.map(Json).map_err(InternalServerErrorException::new)
        }

        #[get("/:id")]
        #[openapi(
            summary = "Get a user by ID",
            tag = "users",
            responses = ((200, "User found"), (404, "User not found"))
        )]
        pub async fn get_one(
            State(s): State<Arc<UserService>>,
            axum::extract::Path(id): axum::extract::Path<i64>,
        ) -> Result<Json<UserRow>, HttpException> {
            s.find_by_id(id).await.map(Json)
        }
    }
    ```
  </Step>
</Steps>

## Customize with OpenApiOptions

When you need to set the API title, version, server URLs, or security schemes, replace `enable_openapi()` with `enable_openapi_with_options(OpenApiOptions { ... })`.

```rust theme={null}
use nestrs::prelude::*;
use nestrs_openapi::OpenApiOptions;
use serde_json::json;

#[tokio::main]
async fn main() {
    NestFactory::create::<AppModule>()
        .enable_openapi_with_options(OpenApiOptions {
            title: "My API".into(),
            version: "1.0.0".into(),
            json_path: "/openapi.json".into(),
            docs_path: "/docs".into(),
            api_prefix: "/api/v1".into(),
            servers: Some(vec![
                json!({ "url": "https://api.example.com", "description": "Production" })
            ]),
            document_tags: Some(vec![
                json!({ "name": "users", "description": "User operations" })
            ]),
            ..Default::default()
        })
        .listen(3000)
        .await;
}
```

All fields on `OpenApiOptions` have defaults — use `..Default::default()` and override only what you need.

## Add schemas to components

Schemas are **not inferred from handler signatures** — the `#[openapi(responses = ...)]` attribute on handlers sets status codes and descriptions but never `content` or `$ref` links. However, Rust types **do** generate schemas: every `#[dto]` struct derives `schemars::JsonSchema` in the same decorator that gives it serde and `validator` derives. Feed those schemas to nestrs-openapi with two helpers:

* `schema_entry::<T>("T")` — builds one `(name, schema)` pair from any `JsonSchema` type (the `schemars::schema_for!` output, serialized as JSON Schema).
* `OpenApiOptions::with_schemas([...])` — merges those entries into `components.schemas`.

OpenAPI 3.1 `components.schemas` values are JSON Schema documents, so the reflected schema drops in unchanged — including `#[serde(rename)]` renames and nested `#[dto]` fields (which arrive as `$ref` + `$defs` chains).

```rust theme={null}
use nestrs_openapi::{OpenApiOptions, schema_entry};

#[dto]
pub struct CreateUserDto {
    #[IsEmail]
    pub email: String,
    #[MinLength(1)]
    pub name: String,
}

let options = OpenApiOptions::default()
    .with_schemas([
        schema_entry::<CreateUserDto>("CreateUserDto"),
        schema_entry::<UserRowDto>("UserRowDto"),
    ]);

NestFactory::create::<AppModule>()
    .enable_openapi_with_options(options)
    .listen(3000)
    .await;
```

<Note>
  Because the `#[dto]` derive expansion references the `schemars` (and `validator`) crate paths directly, your crate needs `schemars = "1"` as a direct dependency. `nestrs` re-exports it as `nestrs::schemars`.
</Note>

Schemas are still **not linked to individual operations** — `schema_entry` / `with_schemas` populate `components.schemas` for tooling, codegen, and client generation; attaching `content.application/json.schema.$ref` to specific operations is on you (post-process the JSON, or merge external fragments as below).

For types outside `#[dto]`, or schemas you want to author by hand, pass them under `OpenApiOptions.components`:

```rust theme={null}
use nestrs_openapi::OpenApiOptions;
use serde_json::json;

OpenApiOptions {
    components: Some(json!({
        "schemas": {
            "UserDto": {
                "type": "object",
                "required": ["id", "email"],
                "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "email": { "type": "string", "format": "email" }
                }
            }
        }
    })),
    ..Default::default()
}
```

<Tip>
  For schema derives on types you don't want to mark `#[dto]`, add `utoipa` with its `ToSchema` and `IntoParams` derives, build a small `utoipa::OpenApi` fragment, serialize it to `serde_json::Value`, and merge it into `OpenApiOptions.components`. One OpenAPI document is served from nestrs; utoipa supplies the schema fragments.
</Tip>

## Global security scheme

Declare a security scheme under `components.securitySchemes` and reference it in the root `security` array to apply it to all operations in the Swagger UI.

```rust theme={null}
use nestrs_openapi::OpenApiOptions;
use serde_json::json;

OpenApiOptions {
    components: Some(json!({
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "JWT"
            }
        }
    })),
    security: Some(vec![json!({ "bearerAuth": [] })]),
    ..Default::default()
}
```

## Per-route security from #\[roles]

When handlers use `#[roles("admin")]`, the macro stores `roles` metadata in `MetadataRegistry`. Setting `infer_route_security_from_roles: true` tells nestrs-openapi to add a `security` array to those operations automatically, so Swagger UI shows a lock icon only on protected routes.

```rust theme={null}
use nestrs_openapi::OpenApiOptions;
use serde_json::json;

OpenApiOptions {
    components: Some(json!({
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "JWT"
            }
        }
    })),
    security: None,                          // no global requirement
    infer_route_security_from_roles: true,   // per-route, keyed off #[roles]
    roles_security_scheme: "bearerAuth".into(),
    ..Default::default()
}
```

Routes with `#[roles("admin")]` get `"security": [{ "bearerAuth": [] }]` in the generated document. Routes without `#[roles]` are left unchanged.

<Warning>
  `infer_route_security_from_roles` is a heuristic that reads metadata, not runtime guard types. Custom guards that do not set `roles` metadata will not trigger the security hint unless you also add `#[roles(...)]` or `#[set_metadata("roles", "...")]` on those handlers.
</Warning>

## Standalone router

If you manage your own Axum router rather than using `NestApplication`, import `openapi_router` from `nestrs-openapi` directly and merge the returned `Router` into your app.

```rust theme={null}
use axum::Router;
use nestrs_openapi::{openapi_router, OpenApiOptions};
use serde_json::json;

fn docs_routes() -> Router {
    openapi_router(OpenApiOptions {
        title: "My API".into(),
        version: "0.1.0".into(),
        json_path: "/openapi.json".into(),
        docs_path: "/docs".into(),
        api_prefix: "/api/v1".into(),
        ..Default::default()
    })
}
```

Set `api_prefix` so the documented paths in `openapi.json` match the actual URLs your application serves.

## Troubleshooting

| Symptom | What to check |
| - | - |
| `/docs` or `/openapi.json` returns 404 | Confirm `features = ["openapi"]` on `nestrs` and that `enable_openapi()` is called before `listen`. |
| Routes missing from the document | Handlers must use `#[routes]` or `impl_routes!` to register in `RouteRegistry`. |
| Schemas are empty | Schemas aren't picked up automatically — pass `#[dto]` types via `schema_entry` / `with_schemas`, hand-author `OpenApiOptions.components`, or merge a `utoipa` fragment. |
| Security not shown per-route | Set `infer_route_security_from_roles: true` and `roles_security_scheme`; add `#[roles(...)]` to the handler. |
| Paths have wrong prefix | Set `api_prefix` on `OpenApiOptions` to match your `set_global_prefix` and controller `version`. |
