---
title: Request Lifecycle
description: Follow one HTTP request through middleware, auth, handlers, and errors so you can trace any endpoint.
---

### How a request enters the app

Open `apps/server/src/app.ts` first. Middleware runs in this order on every request: `requestId()` from `hono/request-id` assigns a correlation id, `structuredLogger` from `@hono/structured-logger` logs start and completion, `cors()` enforces the origin list, then a small redirect strips trailing slashes with a 307. Routes mount after that, so `/api/v1/sales/` becomes `/api/v1/sales` before your handler ever sees it.

The Bun entry `apps/server/src/index.ts` does not run middleware. It calls `ensureLoginAttemptsTable()` from `apps/server/src/core/auth/login-attempts.ts`, reads `PORT` through `apps/server/src/core/config/env.ts` (default 8000), and exports the `app` from `apps/server/src/app.ts` as a default export with `port` and `fetch`.

### What the middleware logs

The `structuredLogger` setup in `apps/server/src/app.ts` writes a debug line on request start and an info line on response with method, path, status, and elapsed milliseconds. Its `skip` check drops `/health` entirely so liveness probes do not flood your logs. It builds the per request logger with `createRequestLogger(...)` from `apps/server/src/core/logging/logger.ts`, passing the request id from `requestId()`, so you can grep one id and see the full story.

### How auth attaches your user

Protected routes call `authMiddleware` from `apps/server/src/core/auth/middleware.ts`. It reads the `Authorization: Bearer <token>` header, verifies the HS256 JWT with `verifyAccessToken` in `apps/server/src/core/auth/token.ts`, and checks claims `sub`, `role`, `branch_id`, and `email` (with `org_id` defaulting to `"org"`). It then re reads the user row so a role change or deactivation takes effect immediately, and throws 401 when the account is inactive. Handlers read the result with `requireUser(context)` from `apps/server/src/core/common/http.ts`, which throws a 401 when nobody is attached.

Auth also pins the tenant. `authMiddleware` calls `applyTenantSearchPath(...)` from `apps/server/src/core/database/tenancy.ts` with the token `org_id`: on Postgres the request runs inside one transaction so the tenant `search_path` dies with it, on SQLite it is a no op.

### How roles gate handlers

After auth, routes add `requireRoles(...)` from `apps/server/src/core/auth/middleware.ts`. An `admin` always passes, everyone else must hold one of the listed roles. The `requireAdmin` helper is just `requireRoles()` with no listed roles, so only admins pass. Expect a 403 with the role name in the message when access is denied.

### How errors become responses

Throw `HttpError` from `apps/server/src/core/common/http.ts` whenever the client did something wrong. The `onError` handler in `apps/server/src/app.ts` turns it into `{ detail }` with your status, maps a Zod failure to a 400 with `[AUTH] Invalid request body`, and turns anything else into a 500. Never leak stack traces; the detail string is the whole contract.

### The full path as a chart

```mermaid
flowchart TD
    A[Client request] --> B[requestId middleware]
    B --> C[structuredLogger, skip /health]
    C --> D[CORS check]
    D --> E[Trailing slash 307]
    E --> F{Mount point requires auth?}
    F -->|No| G[Handler runs]
    F -->|Yes| H[authMiddleware verifies JWT]
    H --> I[requireRoles checks role]
    I --> G
    G --> J{Threw HttpError?}
    J -->|Yes| K[JSON detail + status]
    J -->|No| L[JSON response + logged completion]
```

### Try it with curl

Log in and reuse the token. Feature routes mount under `/api/v1`, so the login URL includes it.

```bash
curl -s -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"pharm1@demo.pharmaflow","password":"Demo@1234"}'
```

```bash
TOKEN="<paste access_token here>"
curl -s http://localhost:8000/api/v1/medicines \
  -H "Authorization: Bearer $TOKEN"
```

`GET /health` stays at the root and needs no token:

```bash
curl -s http://localhost:8000/health
```

### Next steps

- Read /architecture/branches-and-scoping to see how your branch id scopes every query
- Read /architecture/offline-sync to see how devices replay the same handlers
- Browse /api for the endpoint reference behind each route
