# axum-api-template An opinionated Axum web API template modelled after the ASP.NET project layout. ## Philosophy | ASP.NET concept | This template | |---|---| | `Program.cs` bootstrap | `src/main.rs` | | `appsettings.json` + env overrides | `config/*.toml` + `APP__*` env vars | | `IServiceCollection` / DI | `AppState` (shared via Axum `State` extractor) | | Controller classes | `src/handlers/` — one file per resource | | Request / Response DTOs | `src/models/` — `*Request` / `*Response` suffix convention | | `ProblemDetails` | `src/errors/ApiError` — single `IntoResponse` impl | | `app.Use*` middleware pipeline | `src/routes/mod.rs` `build_router()` | | Route groups (`MapGroup`) | `src/routes/v1.rs` nested routers | ## Project layout ``` axum-api-template/ ├── config/ │ ├── default.toml # base config (committed) │ ├── development.toml # dev overrides (committed) │ └── production.toml # prod overrides (committed; no secrets) ├── src/ │ ├── main.rs # entry point — startup sequence │ ├── lib.rs # re-exports all modules for integration tests │ ├── config/ # AppConfig (layered TOML + env vars) │ ├── errors/ # ApiError → consistent JSON error envelope │ ├── handlers/ │ │ ├── health.rs # GET /health/live, /health/ready │ │ └── items.rs # full CRUD scaffold for an example resource │ ├── middleware/ │ │ ├── auth.rs # AuthenticatedUser extractor (JWT stub) │ │ └── request_id.rs # X-Request-ID documentation anchor │ ├── models/ │ │ ├── item.rs # Item domain struct + CreateItemRequest + ItemResponse │ │ └── pagination.rs # PaginationQuery + PagedResponse │ ├── routes/ │ │ ├── mod.rs # build_router() — middleware stack assembly │ │ └── v1.rs # /api/v1/* route table │ └── state/ # AppState (Arc) ├── tests/ │ ├── common/mod.rs # shared test helpers (TestServer factory) │ └── health_test.rs # integration tests for /health/* ├── .env.example ├── .gitignore └── Cargo.toml ``` ## Quickstart ```bash cp .env.example .env # Edit .env — at minimum set APP__DATABASE__URL cargo run # → Listening on 0.0.0.0:8080 ``` ## Configuration Configuration is resolved in order of increasing precedence: 1. `config/default.toml` 2. `config/{APP_ENV}.toml` (set `APP_ENV=production` in your environment) 3. Environment variables prefixed `APP__` with double-underscore separators ```bash # Examples APP__SERVER__PORT=9000 APP__DATABASE__URL=postgres://user:pass@db/myapp APP__AUTH__JWT_SECRET=super-secret ``` ## Naming conventions | Thing | Convention | Example | |---|---|---| | Handler functions | `verb_resource` | `list_items`, `create_item` | | Request DTOs | `{Resource}CreateRequest` | `CreateItemRequest` | | Response DTOs | `{Resource}Response` | `ItemResponse` | | Route modules | version-namespaced | `routes/v1.rs` | | Config env vars | `APP__{SECTION}__{KEY}` | `APP__SERVER__PORT` | | Error variants | PascalCase domain names | `ApiError::NotFound` | ## Endpoints | Method | Path | Description | |---|---|---| | `GET` | `/health/live` | Liveness check | | `GET` | `/health/ready` | Readiness check (dependency health) | | `GET` | `/api/v1/items` | List items (paginated) | | `POST` | `/api/v1/items` | Create item | | `GET` | `/api/v1/items/:id` | Get item by ID | | `PUT` | `/api/v1/items/:id` | Update item | | `DELETE` | `/api/v1/items/:id` | Delete item | ## Error shape All errors return a consistent JSON envelope: ```json { "error": { "status": 404, "code": "NOT_FOUND", "message": "Item 00000000-0000-0000-0000-000000000001 not found", "trace_id": "01924b72-1234-7abc-def0-000000000000" } } ``` ## Next steps - [ ] Replace the in-memory stubs in `handlers/items.rs` with real `sqlx` queries - [ ] Implement JWT validation in `middleware/auth.rs` - [ ] Add `APP__DATABASE__URL` to `.env` and uncomment the DB pool in `state/mod.rs` - [ ] Tighten the CORS policy in `routes/mod.rs` for production - [ ] Add a service layer between handlers and DB for business logic