Create secure JSON APIs with routes, controllers, validation, models, and consistent responses.
01
A route that returns JSON
json() serialises the value, sets the content type, and applies compression and ETags. When a browser asks for HTML, it renders a readable viewer instead—add ?raw=1 to force the raw payload.
server.js
app.get("/api/users/:id", async ({ params, json, abort }) => {
const user = await User.find(params.id);
if (!user) return abort(404, "No user with that id");
return json(user);
});
02
Group endpoints in a controller
One controller per resource keeps routes readable and actions testable.
Never pass a raw body to a model. validate() returns only declared fields, and models drop anything outside fillable—two layers that make mass assignment a non-event.
Webhooks and server-to-server calls authenticate with a signature or bearer token, not a cookie, so exempt those paths and verify the credential yourself.
noderyx.config.js
security: { csrf: { exempt: ["/api/webhooks"] } }
NOTE
Exempting a path removes CSRF only—rate limiting, body limits, and headers still apply.
07
Protect expensive endpoints
The global limiter protects the server; expensive routes deserve their own budget on top of it.
Require authentication before anything costly runs.
Cap input size explicitly rather than relying on the global body limit.
Add a per-user limit for search, export, and AI endpoints.
Set timeouts on outbound calls so one slow dependency cannot pile up requests.
Return 429 with Retry-After instead of queueing work indefinitely.
08
Consistent responses
Pick one response shape and keep it. Clients written against a predictable API need far less defensive code.
One application can render pages and answer JSON. Keep API routes under a prefix so CORS rules, rate limits, and CSRF exemptions can be reasoned about as a group.