# Ring API

ShipClojure uses reitit and ring for handling API calls. All of the main middleware and configuration is done through ring at the handler level. Reitit is used for routing and route-specific middleware like coercion and authentication.

This architecture was chosen because ring & reitit have different approaches to middleware configuration that can build confusion when mixing them together. By keeping the main middleware chain in ring and only using reitit for routing and route-specific concerns, the system becomes easier to reason about.

## Architecture Overview

The handler system is split into two components:

1. **`:handler/reitit`** - Handles routing, default handlers (404, static files, swagger-ui), and route-specific middleware
2. **`:handler/ring`** - Applies the main middleware chain that affects all requests (logging, formatting, sessions, etc.)

## Example Routes

```clojure
(defn api-routes [{:keys [db] :as opts}]
  ["" {:middleware [[mw/wrap-jwt-auth]]}
   ["/account" {:swagger {:tags ["Account API"]}}
    ["/me" {:middleware [[mw/wrap-authenticated]]}
     ["" {:get {:summary "Get account information"
                :handler (auth/get-account db)
                :responses {200 {:body s/user-account}}}}]]
    ["/sign-up" {:post {:summary "Create an account"
                        :handler (auth/sign-up! opts)
                        :responses {201 {:body s/sign-up-response}}
                        :parameters {:body s/create-account}}}]
    ["/log-in" {:post {:summary "Log in to your account"
                       :handler (auth/log-in opts)
                       :responses {200 {:body s/log-in-response}}
                       :parameters {:body s/log-in}}}]]
   ["/health"
    {:get (healthcheck/healthcheck db)}]])
```

Route-level middleware (like `wrap-jwt-auth` and `wrap-authenticated`) runs after the main ring middleware chain but before the final handler.

## Middleware Chain

The middleware chain is in [handler.clj](https://github.com/shipclojure/shipclojure/blob/main/src/clj/saas/web/handler.clj):

```clojure
(-> reitit-handler
    ;; Log and catch exceptions
    (exception/exception-middleware)
    ;; format the response based on accept header
    (muuntaja.middleware/wrap-format-response middleware.formats/instance)
    ;; Log request parameters
    (logger/wrap-log-request-params)
    ;; Merge :body-params parsed by Muuntaja into :params map
    (muuntaja.middleware/wrap-params)
    ;; Specific ring config (params, cookies, session, static files)
    (ring.middleware.defaults/wrap-defaults config)
    ;; format the request. Don't do this on stripe webhooks as we need the :body
    ;; to be intact to verify the request signature
    (mw/if-url-not-in
      #{"/webhooks/stripe"}
      #(muuntaja.middleware/wrap-format-request % middleware.formats/instance))
    ;; Log and catch exceptions
    (exception/exception-middleware)
    ;; negotiate the request & response formats
    (muuntaja.middleware/wrap-format-negotiate middleware.formats/instance)
    ;; Log response and duration
    (logger/wrap-log-response)
    ;; Gzip the response to deliver smaller payload
    (gzip/wrap-gzip)
    ;; Start logging and add ::logger/start-ms to request
    (logger/wrap-log-request-start))
```

Contrary to first instinct, the execution is from the **bottom to the top** and then again to the bottom. Think of it as an onion - requests go in through the outer layers, reach the handler, and responses come back out through the same layers.

### Order of Execution

**Request Phase (bottom to top):**

1. **`logger/wrap-log-request-start`** - Logs request start and adds `:saas.logging/start-ms` to the request for timing. Sets a log context `:req-id` so all logs in the chain share the same request ID for filtering.
2. **`gzip/wrap-gzip`** - On the request phase, does nothing. Will compress the response on the way back.
3. **`logger/wrap-log-response`** - Takes the `start-ms` from earlier middleware and calls next. Will log the response on the way back.
4. **`muuntaja/wrap-format-negotiate`** - Adds `muuntaja/request-format` and `muuntaja/response-format` to the request based on `Content-Type` and `Accept` headers. This information is used by subsequent muuntaja middleware.
5. **`exception/exception-middleware`** (first) - Wraps the rest of the chain in try/catch for potential errors. See [Exception Handling](#exception-handling).
6. **`muuntaja/wrap-format-request`** - Decodes the request body based on `Content-Type` into EDN and adds content to `:body-params`. **Note:** This is conditionally skipped for `/webhooks/stripe` to preserve the raw body for signature verification.
7. **`ring.middleware.defaults/wrap-defaults`** - Configurable middleware from [system.edn](https://github.com/shipclojure/shipclojure/blob/main/resources/system.edn). Adds cookies, session, query params, serves static assets from [public](https://github.com/shipclojure/shipclojure/blob/main/resources/public/README.md), and more.
8. **`muuntaja/wrap-params`** - Merges `:body-params` (from step 6) into `:params` along with query/form params added by `wrap-defaults`.
9. **`logger/wrap-log-request-params`** - Logs all request params from the `:params` key.
10. **`muuntaja/wrap-format-response`** - Does nothing on request phase. Will encode the response body on the way back.
11. **`exception/exception-middleware`** (second) - Another try/catch wrapper. Why two? Because if handlers throw after step 10, errors would bypass response formatting. This middleware catches those errors and generates a response map that goes back through step 10 to be properly formatted.
12. **Reitit Handler** - Routes the request to the appropriate handler based on URI and method. Route-level middleware (like `wrap-jwt-auth`) executes here, before the final handler.
13. **Final Handler** - Executes and returns a response map like `{:status 200 :body {:message "success"}}`.

**Response Phase (top to bottom):**

14. Reitit router passes through the response.
15. Inner exception middleware returns the response (nothing was thrown).
16. **`muuntaja/wrap-format-response`** - Encodes the response body based on `Accept` header (JSON, EDN, or Transit).
17. **`logger/wrap-log-request-params`** - Already logged, passes through.
18. **`muuntaja/wrap-params`** - Passes through (request-only logic).
19. **`ring.middleware.defaults/wrap-defaults`** - May set content-type, charset, not-modified headers.
20. **`muuntaja/wrap-format-request`** - Passes through (request-only logic).
21. Outer exception middleware passes through.
22. **`muuntaja/wrap-format-negotiate`** - Passes through (request-only logic).
23. **`logger/wrap-log-response`** - Logs response status, content-type, and duration of the entire request.
24. **`gzip/wrap-gzip`** - Compresses the response body if the client supports it.
25. **`logger/wrap-log-request-start`** - Passes the response to the HTTP server (http-kit by default).

## Reitit Router Middleware

In addition to the ring middleware chain, reitit applies router-level middleware for coercion:

```clojure
(def router-middleware
  [coercion/coerce-request-middleware
   coercion/coerce-response-middleware])
```

This middleware:

* **`coerce-request-middleware`** - Coerces and validates `:path-params`, `:query-params`, and `:body-params` according to the malli schemas defined in routes (`:parameters`). Populates the `:parameters` key in the request.
* **`coerce-response-middleware`** - Validates response bodies against schemas defined in `:responses`.

## Exception Handling

The custom exception middleware in `saas.web.middleware.exception` catches all exceptions and converts them to appropriate HTTP responses.

**Default Handlers:**

* `::default` - Returns 500 with exception class name
* `:ring.util.http-response/response` - Reads response from `ex-data :response` (for early-exit patterns using `http-response/bad-request!` etc.)
* `:muuntaja/decode` - Returns 400 for malformed request bodies

**Example using http-response for early exit:**

```clojure
(defn my-handler [req]
  (when-not (valid? req)
    ;; This throws, but exception middleware converts it to a proper response
    (http-response/bad-request! {:message "Invalid request"
                                 :cause :validation-failed}))
  ;; Normal response
  (http-response/ok {:data "success"}))
```

## API Documentation

API documentation is available via OpenAPI 3.0:

* **OpenAPI JSON:** `/openapi/openapi.json`
* **Swagger UI:** `/openapi`

Routes can define schemas for automatic documentation:

```clojure
["/account/sign-up" {:post {:summary "Create an account"
                            :description "Create a new user account"
                            :handler (auth/sign-up! opts)
                            :parameters {:body s/create-account}
                            :responses {201 {:body s/sign-up-response}}}}]
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://shipclojure.gitbook.io/shipclojure-docs/ring-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
