# "Routing"
_Path: en/http/router_
> "Routers group endpoints under URL prefixes and apply shared middleware. Endpoints define HTTP handlers."
## Table of Contents
- Routing
## Content
# Routing
An `http.router` groups endpoints under a URL prefix and applies shared middleware. Each `http.endpoint` defines an HTTP handler.
**Classification: routing reference.** Configuration blocks are partial registry
fragments unless they include a namespace and every referenced entry. Handler
blocks use application-owned function IDs rather than defining a data layer.
## Architecture
```mermaid
flowchart TB
S[http.service
:8080] --> R1[http.router
/api]
S --> R2[http.router
/admin]
S --> ST[http.static
/]
R1 --> E1[GET /users]
R1 --> E2[POST /users]
R1 --> E3["GET /users/{id}"]
R2 --> E4[GET /stats]
R2 --> E5[POST /config]
```
Entries reference parents via metadata:
- Routers: `meta.server: app:gateway`
- Endpoints: `meta.router: app:api`
## Router Configuration
```yaml
- name: api
kind: http.router
meta:
server: gateway
prefix: /api/v1
middleware:
- cors
- compress
options:
cors.allow.origins: "*"
post_middleware:
- endpoint_firewall
```
| Field | Type | Description |
|-------|------|-------------|
| `meta.server` | Registry ID | Parent HTTP server |
| `prefix` | string | URL prefix for all routes |
| `middleware` | []string | Pre-match middleware |
| `options` | map | Middleware options |
| `post_middleware` | []string | Post-match middleware |
| `post_options` | map | Post-match middleware options |
## Endpoint Configuration
```yaml
- name: get_user
kind: http.endpoint
meta:
router: api
method: GET
path: /users/{id}
func: app.users:get_user
```
| Field | Type | Description |
|-------|------|-------------|
| `meta.router` | Registry ID | Parent router |
| `method` | string | HTTP method: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `TRACE`, or `*` for any method |
| `path` | string | URL path pattern (starts with `/`) |
| `func` | Registry ID | Handler function |
## Path Parameters
Use `{param}` syntax for URL parameters:
```yaml
- name: get_post
kind: http.endpoint
meta:
router: api
method: GET
path: /users/{user_id}/posts/{post_id}
func: get_user_post
```
Access in handler:
```lua
local http = require("http")
local function handler()
local req, req_err = http.request()
if req_err then return nil, req_err end
local user_id, user_err = req:param("user_id")
if user_err then return nil, user_err end
local post_id, post_err = req:param("post_id")
if post_err then return nil, post_err end
return {user_id = user_id, post_id = post_id}
end
```
### Wildcard Paths
Capture remaining path segments with `{param...}`:
```yaml
- name: serve_files
kind: http.endpoint
meta:
router: api
method: GET
path: /files/{filepath...}
func: serve_file
```
The wildcard matches the remaining segments, so a request like `GET /api/v1/files/docs/guides/readme.md` is dispatched to the handler. The captured tail is read with `req:param` under the name without the trailing dots:
```lua
local filepath = req:param("filepath") -- "docs/guides/readme.md"
```
The wildcard must be the last segment in the path.
## Route Precedence
All routers register their endpoints into a single pattern set, prefixed by the router's `prefix`, and Go's `ServeMux` decides which pattern serves a request. Its rules apply unchanged:
- The most specific pattern wins. A pattern is more specific than another when it matches a strict subset of that pattern's requests, so `/users/admin` beats `/users/{id}`, and `/files/{name}` beats `/files/{path...}`.
- A pattern with a method is more specific than the same path without one, so a `GET` endpoint takes precedence over a `*` endpoint on the same path for `GET` requests.
- A trailing `{path...}` or `/` matches an entire subtree and loses to any pattern that matches a subset of it.
- Matching is on the cleaned, decoded path; specificity never depends on registration order.
Two patterns can also conflict outright: neither is more specific than the other, yet they overlap, as with `/users/{id}/settings` and `/users/admin/{section}`. This is a configuration error. The router surfaces it when it rebuilds, the rebuild fails, and the previous route set stays in service.
## Handler Functions
Endpoint handlers use the `http` module to access request and response objects. See [HTTP Module](lua/http/http.md) for the request and response API reference.
```lua
local http = require("http")
local funcs = require("funcs")
local function handler()
local req, req_err = http.request()
if req_err then return nil, req_err end
local res, res_err = http.response()
if res_err then return nil, res_err end
local user_id, param_err = req:param("id")
if param_err then return nil, param_err end
local user, call_err = funcs.call("app.users:get_user", user_id)
if call_err then return nil, call_err end
local status_err = res:set_status(http.STATUS.OK)
if status_err then return nil, status_err end
local write_err = res:write_json(user)
if write_err then return nil, write_err end
return true
end
return { handler = handler }
```
## Middleware Options
Middleware options use dot notation with the middleware name as prefix:
```yaml
middleware:
- cors
- ratelimit
- token_auth
options:
cors.allow.origins: "https://app.example.com"
cors.allow.methods: "GET,POST,PUT,DELETE"
ratelimit.requests: "100"
ratelimit.window: "1m"
token_auth.store: "app:tokens"
token_auth.header.name: "Authorization"
```
Post-match middleware uses `post_options`:
```yaml
post_middleware:
- endpoint_firewall
post_options:
endpoint_firewall.action: "access"
```
## Pre-Handler and Post-Match Middleware
**Pre-handler** (`middleware`) runs after the server selects a route but before route parameters and endpoint metadata are attached to the request context:
- CORS (handles OPTIONS preflight)
- Compression
- Rate limiting
- Real IP detection
- Token authentication (context enrichment)
**Post-match** (`post_middleware`) runs after route parameters and endpoint metadata are attached:
- Endpoint firewall (needs route info for authorization)
- Resource firewall
- WebSocket relay
```yaml
middleware: # Before endpoint metadata: matched routes only
- cors
- compress
- token_auth # Enriches context with actor/scope
post_middleware: # Post-match: matched routes only
- endpoint_firewall # Uses actor from token_auth
```
Token authentication belongs in the pre-handler chain because it enriches the request context before authorization. Authorization middleware such as endpoint_firewall belongs in the post-match chain because it needs the matched endpoint ID. Unmatched requests do not run either router chain.
## Router and Endpoint Wiring
This example defines the list handler entry. The `app:get_user_by_id` and `app:create_user` function IDs refer to handlers defined elsewhere in the same namespace.
```yaml
version: "1.0"
namespace: app
entries:
# Server
- name: gateway
kind: http.service
addr: ":8080"
lifecycle:
auto_start: true
# API Router
- name: api
kind: http.router
meta:
server: gateway
prefix: /api/v1
middleware:
- cors
- compress
- ratelimit
options:
cors.allow.origins: "https://app.example.com"
ratelimit.requests: "100"
ratelimit.window: "1m"
# Handler function
- name: get_users
kind: function.lua
source: file://handlers/users.lua
method: list
modules:
- http
- json
- sql
# Endpoints
- name: list_users
kind: http.endpoint
meta:
router: api
method: GET
path: /users
func: get_users
- name: get_user
kind: http.endpoint
meta:
router: api
method: GET
path: /users/{id}
func: app:get_user_by_id
- name: create_user
kind: http.endpoint
meta:
router: api
method: POST
path: /users
func: app:create_user
```
## Protected Routes
The following configuration separates public routes from routes that require authentication and authorization:
```yaml
entries:
# Public routes (no auth)
- name: public
kind: http.router
meta:
server: gateway
prefix: /api/public
middleware:
- cors
# Protected routes
- name: protected
kind: http.router
meta:
server: gateway
prefix: /api
middleware:
- cors
- token_auth
options:
token_auth.store: app:tokens
post_middleware:
- endpoint_firewall
```
## See Also
- [Server](http/server.md) - HTTP server configuration
- [Static Files](http/static.md) - Static file serving
- [Middleware](http/middleware.md) - Available middleware
- [HTTP Module](lua/http/http.md) - Lua HTTP API
## Navigation
Previous: "HTTP Server" (http/server)
Next: "HTTP Endpoints" (http/endpoint)