Routing
Routers group endpoints under URL prefixes and apply shared middleware. Endpoints define HTTP handlers.
Architecture
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
- 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
- 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:
- 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:
local http = require("http")
local function handler()
local req = http.request()
local user_id = req:param("user_id")
local post_id = req:param("post_id")
-- ...
end
Wildcard Paths
Capture remaining path segments with {param...}:
- 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:
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/adminbeats/users/{id}, and/files/{name}beats/files/{path...}. - A pattern with a method is more specific than the same path without one, so a
GETendpoint takes precedence over a*endpoint on the same path forGETrequests. - 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 for the complete API.
local http = require("http")
local function handler()
local req = http.request()
local res = http.response()
local user_id = req:param("id")
local user = get_user(user_id)
res:set_status(http.STATUS.OK)
res:write_json(user)
end
return { handler = handler }
Middleware Options
Middleware options use dot notation with the middleware name as prefix:
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:
post_middleware:
- endpoint_firewall
post_options:
endpoint_firewall.action: "access"
Pre-Match vs Post-Match Middleware
Pre-match (middleware) runs before route matching:
- CORS (handles OPTIONS preflight)
- Compression
- Rate limiting
- Real IP detection
- Token authentication (context enrichment)
Post-match (post_middleware) runs after route is matched:
- Endpoint firewall (needs route info for authorization)
- Resource firewall
- WebSocket relay
middleware: # Pre-match: all requests to this router
- cors
- compress
- token_auth # Enriches context with actor/scope
post_middleware: # Post-match: matched routes only
- endpoint_firewall # Uses actor from token_auth
endpoint_firewall which uses the actor set by token_auth.
Complete Example
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
Common pattern with authentication:
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 configuration
- Static Files - Static file serving
- Middleware - Available middleware
- HTTP Module - Lua HTTP API