NODERYX
FRAMEWORK v0.7.1

GETTING STARTED

BUILDING

PRODUCTION

DEVICES

REFERENCE

NODERYX / DOCS / BUILDING / 06
BUILDING / 06

API development

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.

app/Controllers/UserController.js
export class UserController extends Controller { async index() { return this.json(await User.all()); } async show() { return this.json(await User.find(this.params.id)); } async store() { return this.json(await User.create(this.body), 201); } async update(){ return this.json(await User.update(this.params.id, this.body)); } async destroy(){ await User.delete(this.params.id); return this.json({ deleted: true }); } }
03

Validate before you write

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.

app/Controllers/UserController.js
async store() { const { valid, values, errors } = this.context.validate({ email: "required|email|max:255", name: "required|string|min:2|max:100" }); if (!valid) return this.abort(422, Object.values(errors)[0]); return this.json(await User.create(values), 201); }
04

Status codes that mean something

abort() produces the right status with a message you control, and the framework renders JSON or HTML depending on what the client asked for.

201 Created. Return the new resource.
401 / 403 Not signed in / signed in but not permitted.
404 The resource does not exist.
419 CSRF token missing or invalid.
422 Validation failed. Return the first useful message.
429 Rate limited. Retry-After is set for you.
05

Authenticate with sessions

For a browser client, the signed session cookie is the simplest correct answer—no token storage, no refresh dance, and CSRF already covered.

server.js
app.get("/api/me", ({ session, abort, json }) => { if (!session.userId) return abort(401); return json({ userId: session.userId }); });
06

Machine clients and CSRF

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.

JSON
// success { "data": { "id": 1, "name": "Ada" } } // failure { "error": { "status": 422, "message": "Email is required" } }
09

Serving both a site and an API

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.

server.js
app.get("/", HomeController.handle("index")); app.get("/api/posts", PostController.handle("index")); app.post("/api/posts", PostController.handle("store"));
NOTE

The same API serves your packaged mobile and native builds—see MOBILE_API_URL on the device pages.