# {{project_name}}

A small YAZA HTTP API starter with one study case: a product catalog. The example keeps the same controller, service, interface, implementation, repository, and model boundaries used by larger applications.

## Run

```sh
yaza check
yaza run
```

The API listens on `127.0.0.1:3000` by default. Choose a preferred port with `yaza run --port 8080` or `YAZA_PORT=8080 yaza run`. If the preferred port is occupied, YAZA tries the following ports and prints the address it actually selected. `YAZA_HOST` changes the IPv4 bind address. HTTP `Host` values are checked against an exact allowlist: loopback development defaults to `127.0.0.1`, `localhost`, and the configured loopback address; non-loopback binds must set `YAZA_ALLOWED_HOSTS` explicitly. Configure a comma-separated list of host names without ports or wildcards, for example `YAZA_ALLOWED_HOSTS=api.example.com,api.internal`. The request port is ignored for matching. When a reverse proxy forwards the public `Host`, include that public host name. This host check complements route authentication and TLS; it does not replace either.

HTTP request, idle keep-alive, response-write, and shutdown grace deadlines default to 5,000 ms. Shutdown grace covers both queued response draining and worker termination. Set `YAZA_HTTP_REQUEST_TIMEOUT_MS` to an integer from 1,000 to 300,000 ms, `YAZA_HTTP_KEEP_ALIVE_TIMEOUT_MS` from 1,000 to 60,000 ms, `YAZA_HTTP_RESPONSE_WRITE_TIMEOUT_MS` from 1,000 to 300,000 ms, or `YAZA_HTTP_SHUTDOWN_GRACE_MS` from 1,000 to 120,000 ms. The startup summary reports the effective values, and keep-alive responses advertise the configured idle timeout in seconds. If a handler ignores cancellation and is still running when shutdown grace expires, YAZA exits with failure rather than freeing runtime state under that worker.

The HTTP server accepts at most 64 active connections by default. Set `YAZA_HTTP_MAX_CONNECTIONS` to an integer from 1 to 127 to change this bound; the hard maximum reserves one readiness event for the listening socket. The startup summary prints the selected capacity. When every slot is occupied, excess accepted TCP connections are closed without an HTTP response; `/metrics` reports their count as `yaza_http_connections_rejected_total`. Process-local `EMFILE` pauses listener readiness for one second before retrying and increments `yaza_http_accept_backoffs_total`.

The API worker pool admits at most as many jobs as there are connections by default. Set `YAZA_API_MAX_IN_FLIGHT` to an integer from 1 through the configured connection capacity to lower that limit. The bound includes queued work, running handlers, and completed results waiting for event-loop delivery. When the pool is full, additional controller or readiness work receives HTTP 503 with a stable overload code; `/healthz` and `/metrics` remain available.

Set `YAZA_CORS_ORIGIN` to one exact `http://` or `https://` origin for the server-wide browser policy. Routes and route groups may declare `cors "https://origin"`; group policies inherit through nested groups, and a route declaration overrides its group. A route-specific origin takes precedence over the global origin for that route. Origins cannot contain paths. Preflight requests may use the common `Accept`, `Authorization`, and `Content-Type` headers plus custom headers declared by the matched controller; undeclared custom headers are rejected.

To create a release binary:

```sh
YAZA_BUILD_MODE=release yaza build src/main.yz -o dist/{{project_name}}
./dist/{{project_name}}
```

`yaza run` is the development runner. There is no separate `yaza run prod` mode; use a release build and configure its environment explicitly for deployment.

## Routes

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/` | Welcome response |
| `GET` | `/api/products` | List products; accepts `limit` and `offset` query parameters |
| `GET` | `/api/products/{id}` | Read one product |
| `POST` | `/api/products` | Create a product |
| `PUT` | `/api/products/{id}` | Replace a product |
| `PATCH` | `/api/products/{id}` | Update selected product fields |
| `DELETE` | `/api/products/{id}` | Delete a product |

The runtime also exposes `/healthz`, `/readyz`, and `/metrics` for service operations. `/metrics` reports HTTP request/response counts, active and maximum connections, the `yaza_http_connections_rejected_total` overload counter, the `yaza_http_accept_backoffs_total` descriptor-pressure counter, worker queue/execution gauges and totals for accepted, rejected, failed, cancelled, and timed-out jobs, plus PostgreSQL pool state when configured. When `DATABASE_URL` is set, Product CRUD, startup, and readiness reuse YAZA's bounded process-local PostgreSQL pool. It defaults to 8 connections per database identity, caps at 64 per identity and 128 process-wide, waits at most 5 seconds for a lease, expires idle connections after 60 seconds, and reports pool state through `/metrics`. The pool safely coordinates concurrent leases. Controller and readiness handlers run on the bounded API worker pool (default 4 threads; set `YAZA_API_WORKERS` from 1 to 32). `YAZA_API_MAX_IN_FLIGHT` sets the maximum combined queued, executing, and completed-but-undelivered handler jobs; it defaults to connection capacity and can be lowered to control overload. At the limit, new handler/readiness work receives a safe HTTP 503 response, while health and metrics remain event-loop-served. Workers own copied request state and return results to the event loop, which alone manages sockets and writes responses. Pool acquisition and native TCP/TLS readiness waits check `http_request_cancelled() -> Boolean` at intervals of up to 50 ms. PostgreSQL protocol calls, synchronous DNS resolution, and other blocking/native calls still occupy a worker until they return; the language does not yet expose async/await.

## Route authentication

Routes and route groups support `auth required` for the shared `YAZA_API_TOKEN`, `auth public` for an explicit public route, and `auth jwt` for signed access tokens. A shared `YAZA_API_TOKEN` must be 32–1024 ASCII bytes from the Bearer token character set. Run `yaza secret token` to generate 64 lowercase hex characters from 32 operating-system CSPRNG bytes, then store it in a deployment secret manager or local `.env`; set local secret files to mode `0600` with `chmod 600 .env`. YAZA refuses unsafe `.env` files and ignores reserved process, loader, and compiler-control keys; the token length check cannot detect a predictable manually chosen value. During a shared-token rotation, configure two distinct values in `YAZA_API_TOKEN` and `YAZA_API_TOKEN_SECONDARY`; both remain accepted. Roll out the new value as secondary everywhere, then switch the primary while retaining the old value as secondary, move clients after all servers accept both, and remove the secondary after the migration window. This is an overlap mechanism, not an immediate central revocation list. A JWT-protected route requires `YAZA_JWT_SECRET`, `YAZA_JWT_ISSUER`, and `YAZA_JWT_AUDIENCE`. The current verifier accepts compact JWS with HS256, requires `typ: JWT`, exact issuer and an `aud` string or non-empty string array containing the configured value, and an integer `exp`; optional integer `nbf` and `iat` are checked. The same `yaza secret token` command can generate a 256-bit JWT secret; store it in a deployment secret manager or local `.env`. A token issuer must create compatible HS256 tokens with the same secret; OIDC providers using asymmetric signatures are not supported. A matched JWT handler can read verified top-level string claims with `http_auth_claim_text("sub") -> Option[Text]`; missing/non-string claims return `none`, decoded values are capped at 1023 UTF-8 bytes, and the request-scoped payload is wiped after dispatch. Public and shared-Bearer handlers cannot obtain claims from spoofed headers. YAZA does not issue tokens or provide a typed principal, central role/ownership decisions, login, refresh, revocation, JWT key rotation, OIDC/JWKS, or asymmetric algorithms. The HTTP listener does not provide TLS; place a trusted HTTPS reverse proxy or load balancer at the deployment edge.

JWT-only non-loopback binding is allowed only when at least one route uses `auth jwt` and every declared route has an explicit authentication policy. This prevents an omitted route policy from silently becoming public. OpenAPI marks JWT routes with Bearer security and `x-yaza-authentication: jwt-hs256`; Postman generates a Bearer request for those routes.

## Layered example

`src/routes/products.yz` maps HTTP methods and paths to controller functions. Nested `group "/prefix" [auth required|auth public|auth jwt] [rate_limit REQUESTS per WINDOW_MS] [cors "https://origin"]:` blocks compose static route prefixes and inherit authentication, per-client rate-limit, and exact-origin CORS policies; a route-level policy overrides its group. `route GET "/{id}" ...` inside a group is resolved to its full URL for the runtime, OpenAPI, and Postman. Request declarations in `src/controllers/` describe path, query, header, and body fields. The controller implementation reads those validated request values once and assembles a `ProductInput`; it passes that typed value to the service and repository. The service and repository do not reach back into implicit HTTP request state. `src/application.yz` is the composition root that imports executable YAZA implementation modules. Service and repository interface bindings are checked by the compiler.

For example, in `src/controllers/ProductController.yz`:

```yaza
body name: Text
body priceMinor: Number
body currency: Text
body available: Boolean
```

The `body` lines declare fields that YAZA reads and validates from each HTTP request sent by Postman or another client. The request payload is not hard-coded in the controller. For scalar fields, Postman and Swagger/OpenAPI generation create a small placeholder example from the declared types (`"sample"`, `1`, or `true`). Add `example json {...}` to show a more realistic payload; for a request body declared as a `shape`, this example is currently required by the compiler. It only affects generated documentation and clients, never the endpoint's runtime input. The earlier `body json {...}` spelling is still accepted as a compatibility alias.

`src/interfaces/ProductInput.yz` is the request DTO shared by the layers. Create, replace, and patch handlers populate its optional body fields from the actual incoming request; find and delete populate its path ID. The repository validates the fields required by each operation. List filters use explicit `limit` and `offset` parameters. This keeps HTTP parsing in the controller and makes service/repository functions easier to reuse and test.

The starter uses in-memory data by default. Configure `DATABASE_URL` and apply `migrations/` with `yaza db migrate` to use PostgreSQL. API database access uses YAZA's PostgreSQL protocol and parameterized statements; the generated API does not require `libpq`.

## Generate API clients and documentation

```sh
yaza generate --postman
yaza generate --openapi
yaza generate --swagger
```

Run `yaza generate` with no flag to generate all three artifacts under `docs/api/`: `{{project_name}}.postman_collection.json`, `openapi.json`, and `swagger.json`. The collection includes each route's method, absolute default URL, query/path variables, headers, and body example. The OpenAPI and Swagger files contain the same Swagger-compatible OpenAPI 3.1 document. Use `--postman`, `--openapi`, or `--swagger` to generate only one artifact, and import the collection into Postman or either specification into Swagger UI.

## Project layout

```text
src/
  application.yz
  main.yz
  routes/products.yz
  controllers/
  services/
  repositories/
  interfaces/
  models/
  errors/
migrations/
docs/api/
tests/
yaza.toml
```

Run `sh tests/http-smoke.sh` for the Product CRUD smoke check and `sh tests/generation-smoke.sh` for Postman and Swagger/OpenAPI output checks.
