Request Pipeline
Every client request passes through the same five-stage pipeline before reaching your handler. Understanding the order clarifies why certain status codes are returned.
Expanded detail
Version check: The payload is validated against the expected RoExpress protocol version. A version mismatch is recorded as a Tamper strike and the request is rejected silently | no response is sent to the client.
TokenBucket: The player's token bucket is checked. If empty, the request is rejected immediately with 429. Tokens refill continuously at the configured refillRate per second.
Middleware: Each registered middleware runs in registration order. Return false to send 403 and stop processing. An unhandled error sends 500.
Route matching: The Router finds the best matching pattern. No match → 404 (also a Tamper signal). Typed param coercion failure → 400, or your custom app:OnParamError() handler.
Handler dispatch: Your handler is called. Unhandled errors send 500. If the handler exits without res:Send() or res:Error(), the response is finalized as a 200 with nil data.
Status codes reference
| Code | Stage | Cause |
|---|---|---|
| 200 | Handler | res:Send(data) or implicit empty response |
| 400 | Route matching | Typed param coercion failure |
| 403 | Middleware | Middleware returned false |
| 404 | Route matching | No route matched the request path |
| 429 | TokenBucket | Player's token bucket is empty |
| 500 | Middleware or Handler | Unhandled Lua error |
Ports run the same pipeline
Each Port has its own independent pipeline instance | its own TokenBucket, its own middleware stack, its own Router. A 429 on the combat port does not affect the inventory port.
See also
App | middleware and routing · TokenBucket | rate limiting · Tamper | version check & exploit detection · Router | route matching · Reference Overview