Using the API Module
The API module is the HTTP layer of backendjs. A request flows through a small trie router (api.app) that dispatches to a chain of middleware handlers. Every built-in feature — body parsing, uploads, CORS, CSRF, rate limiting, validation, static files, redirects — is just a middleware you can mount explicitly in code or enable from config.
| Piece | Role |
|---|---|
api.Router / api.app |
Trie-based router that matches method + path and runs matching handlers in priority order. |
middleware.body |
Parse JSON / form-urlencoded request bodies into context.body. |
middleware.multipart |
Parse multipart/form-data uploads into context.files + context.body. |
middleware.cors |
Set CORS headers and answer preflight OPTIONS. |
middleware.csrf |
Enforce Origin / Sec-Fetch-Site checks on unsafe methods. |
middleware.limiter |
Rate limit by IP, path, session or user. |
middleware.validate |
Validate query/body/path params (and optionally rate limit by a param value). |
middleware.routing |
Config-driven URL rewrites and redirects. |
middleware.static |
Serve static files from the configured web folders. |
const { api, middleware } = require("backendjs");
const { body, cors, csrf, limiter, validate, routing, static: statik } = middleware;
app.start({ api: true });
Every middleware is a (context, next) function, or an object with a handle(context, next) method. Call next() to pass control on, next(err) to stop with an error, or send a response directly (context.send, context.reply, context.sendFile).
The router
api.app is an instance of api.Router, a simple prefix-tree (trie) router. You register routes with use or the method shortcuts get/post/put/patch/delete/all:
api.app.get("/users/:id", handler); // named param -> context.params.id
api.app.get("/api/:type/*", handler); // wildcard -> context.params['0']
api.app.post("/data", body, handler); // chain multiple handlers
api.app.all("/health", handler); // any method
Matching notes:
:namecaptures a path segment intocontext.params.name;*captures the rest into indexed params ('0','1', …).- When several routes match, all matching handlers run, ordered by an auto-assigned priority number.
- Set an explicit priority with
#NUMin the method to control order regardless of registration order — e.g.api.app.use("GET#0", "/*", guard)runs first. This is how global middleware inserts itself ahead of app handlers. - A handler can call
next("restart")aftercontext.setUrl(...)to re-run routing against the new path (used byrouting).
Most middleware supports two modes:
- Programmatic — mount it yourself on specific paths in code.
- Global config — set
middleware-<name>-enable = trueand letconfigureMiddlewarewire the routes on startup. Some also supportenable = fixed, which registers only what's in the config at start (no dynamic per-request lookups) while still allowing existing rules to be updated.
Body parser
Parses JSON and x-www-form-urlencoded request bodies into context.body. Default max size is 64k; reads are guarded by a timeout to defend against slow-body attacks.
// Programmatic
api.app.post("/api/data", body, (context) => {
if (context.body?.id) { ... }
});
# Global — parse bodies for all POST/PUT/PATCH automatically
middleware-body-enable = true
middleware-body-max-size = 128000
middleware-body-content-type = text/xml, image/png # extra content types
Multipart uploads
Parses multipart/form-data using formidable. Files land in context.files, other fields in context.body. Temp files are cleaned up automatically when the request context is destroyed.
api.app.post("/upload", (context) => {
if (context.files.file) console.log(context.files.file.path, context.files.file.name);
});
middleware-multipart-enable = true
middleware-multipart-max-size = 25000000
middleware-multipart-max-files = 10
CORS
Sets the access-control-* headers and short-circuits preflight OPTIONS requests with 204. Defaults allow all origins with credentials and the common methods.
api.app.use("/api/*", { origin: "*", headers: ["bk-sid"], handle: cors.handle });
middleware-cors-origin = https://app.host.com
middleware-cors-headers = bk-sid
middleware-cors-max-age = 86400
CSRF protection
Guards unsafe methods (everything except GET/HEAD) by requiring and checking the Origin and Sec-Fetch-Site headers. Protection is explicit — only paths you configure are checked. Any matched path requires both headers to be present.
middleware-csrf-enable = true
# Only allow specific origins for /account
middleware-csrf-origin-/account/* = https://host.com, http://localhost
# Only allow same-origin for the whole API
middleware-csrf-sec-fetch-site-/api/* = same-origin
api.app.post("/account", { origin: ["host1.com"], secFetchSite: "same-origin", handle: csrf.handle });
Rate limiter
Throttles requests by IP, path, session, or user. Like CSRF, there are no defaults — only configured paths are limited. rate (requests per interval) is required; add interval, ttl, etc. as needed.
ip— per client IP.path— a single global bucket for the path.session— session id parsed from cookie/header (no verification).user— verified authenticated user (mount after the users guard).
Path wildcards matter: /api/* rates all sub-paths together, while /api/:id/* rates each :id value separately.
middleware-limiter-enable = true
# 100 req/s per IP for the API
middleware-limiter-ip-*-/api/* = rate:100, ttl:900000
# 1 login/s per IP
middleware-limiter-ip-post-/login = rate:1
api.app.post("/account", { ip: { rate: 100 }, path: { rate: 200 }, handle: limiter.handle });
Validate
Validates query, body, or path params before your handler runs, returning an error immediately on bad input. Validated/coerced values are written back so later handlers reuse them. It can also rate limit by a parameter's value — handy for multi-tenant limits keyed by accountId, clientId, etc.
middleware-validate-enable = true
# Require a numeric accountId in the path, allow 100 req/s per account
middleware-validate-params-get,post-accountId-/account/:accountId/* = type:int,required:true,min:100000,rate:100
# Validate login email on POST /login
middleware-validate-body-post-login-/login = type:email,max:128,required:true
Rate options use a rate_ prefix (rate_interval, rate_ttl, rate_user, rate_session, …). Because the router only extracts path params without checking them, this is also the way to enforce a type/format on :params.
api.app.post("/account", {
body: {
accountId: { type: "int", required: true, rate: 100 },
amount: { type: "number", max: 1000 },
},
handle: validate.handle,
});
Routing (rewrite & redirect)
Config-driven URL rewriting and redirects. Prefix the target with a 30X code to redirect; otherwise the URL is rewritten and routing restarts. Values support @PATH@, @SEARCH@ and similar placeholders.
middleware-routing-enable = true
# Rewrite: serve the SPA entry for all /app paths
middleware-routing-/app/* = /index.html
# Redirect anonymous users to the login page
middleware-routing-/login/* = 302/login.html?path=@PATH@
To rewrite from code:
api.app.get("/old/path", (context, next) => {
context.setUrl("/new/path");
next("restart");
});
Static files
Serves static assets from the configured web folders (app.path.web, which includes imported packages), plus the built-in backendjs web folder for the bundled Alpine/Bootstrap assets. Supports caching headers, ETag, Last-Modified, and pre-compressed (gzip/br/zstd) files.
middleware-static-enable = true
middleware-static-max-age = 86400000
middleware-static-etag = true
middleware-static-precompressed = \.js$|\.css$
app-path-web = /path/to/public/files
api.app.get("/blog/*", { root: "dist/", precompressed: /\.js$/, handle: statik.handle });
api.app.get("/public/*", { root: "web", noCache: true, handle: statik.handle });
Static routing uses a high priority number (9999) so it runs after your application handlers, acting as a fallback.
Putting it together
A typical global config chains the middleware in priority order so cross-cutting concerns run before your handlers:
# Parse bodies and uploads
middleware-body-enable = true
middleware-multipart-enable = true
# Security
middleware-csrf-enable = true
middleware-csrf-sec-fetch-site-/api/* = same-origin
# Throttling + validation
middleware-limiter-enable = true
middleware-limiter-ip-post-/login = rate:1
middleware-validate-enable = true
middleware-validate-body-post-login-/login = type:email,max:128,required:true
# Static assets last
middleware-static-enable = true
Or wire it explicitly in code for full control over the chain:
const { api, middleware } = require("backendjs");
const { body, cors, csrf, limiter, validate } = middleware;
api.app.use("/api/*", cors, csrf, limiter);
api.app.post("/api/data", body, validate, (context) => {
context.reply(null, { ok: true });
});
Quick reference
Router (api.app)
get/post/put/patch/delete(path, ...handlers)— register per methodall(path, ...handlers)— any methoduse([method[#priority]], path, ...handlers)— general registrationcontext.params— captured:nameand*valuesnext()/next(err)/next("restart")— continue, fail, or re-route
Middleware (middleware.*) — each has a handle(context, next) and a <name>-enable config
body— JSON / form bodies →context.bodymultipart— uploads →context.filescors— CORS headers + preflightcsrf— Origin / Sec-Fetch-Site checkslimiter— rate limit by ip/path/session/uservalidate— param validation (+ optional rate limit)routing— rewrite / redirectstatic— serve static files