NODERYX
FRAMEWORK v0.7.1

GETTING STARTED

BUILDING

PRODUCTION

DEVICES

REFERENCE

NODERYX / DOCS / GETTING STARTED / 06
GETTING STARTED / 06

Writing your first code

One hello world, grown step by step into a function, a view, a controller, a model, a command, and a scheduled task.

01

What you are about to build

Every part of a Noderyx application can be introduced with the same tiny feature, so this page starts with a route that says hello and keeps extending it until it uses every layer you will actually write. Nothing here is throwaway—each step is the shape you would use in real code.

  • A route handler: the smallest unit of work.
  • A view: the same greeting as HTML instead of JSON.
  • A controller: where the logic moves once it outgrows the route.
  • Your own functions: plain modules, imported normally.
  • A model: the greeting stored and read back.
  • A command: the same work, run from a terminal.
  • A scheduled task: that command, run on a timer.
NOTE

Follow it in a scratch project. npm run dev reloads on every save, so you never restart anything by hand.

02

Step 1 — a function on a route

A handler is just a function that receives the request context and returns a response. This is the whole of Noderyx's request model; everything later is organisation, not new machinery.

server.js
// server.js app.get("/hello", ({ json }) => json({ message: "Hello, world" }));
NOTE

Open http://localhost:3000/hello. A browser asking for HTML gets a readable viewer; a client asking for JSON gets JSON.

03

Step 2 — read input

Query strings and path parameters arrive already parsed, so reading a name needs no helper. Give every input a fallback: a handler should never depend on a value the caller might not send.

server.js
app.get("/hello", ({ query, json }) => json({ message: `Hello, ${query.name ?? "world"}` })); app.get("/hello/:name", ({ params, json }) => json({ message: `Hello, ${params.name}` }));
NOTE

Try /hello?name=Ada and /hello/Ada.

04

Step 3 — say it in a view

A .noderframe view is indentation-based: a tag, optional classes, optional attributes, then quoted text. Generate the file, then render it with the data it needs.

TERMINAL
npx noderyx make:view hello
05

…and the view itself

Placeholders are HTML-escaped by the compiler, so a name containing markup is printed, never executed.

resources/views/hello.noderframe
main.cool-container h1 "Hello, {{name}}" p.cool-muted "Rendered on the server, no client framework required." if greetings ul for greeting in greetings li "{{greeting.body}}" else p "No greetings saved yet."
NOTE

Render it with app.get("/hello", ({ query, render }) => render("hello", { name: query.name ?? "world" }));

06

Step 4 — move it into a controller

Once a route grows past a few lines, it belongs in a controller. handle("action") returns a route handler and builds a fresh instance per request, so no state leaks between callers.

TERMINAL
npx noderyx make:controller HelloController
07

…and the controller it writes

The context you destructured in the route is available through named getters, and the response helpers are the same ones.

app/Controllers/HelloController.js
import { Controller } from "noderyx-framework"; export class HelloController extends Controller { async index() { const name = this.query.name ?? "world"; return this.render("hello", { name }); } async show() { if (!this.params.name) return this.abort(400, "A name is required"); return this.json({ message: `Hello, ${this.params.name}` }); } }
NOTE

Route them with app.get("/hello", HelloController.handle("index")) and app.get("/hello/:name", HelloController.handle("show")).

08

Step 5 — write your own functions

Noderyx adds no rules here. Your helpers are ordinary ES modules, imported normally. Keep them free of request and response objects so they stay testable and reusable from a command later.

app/Support/greeting.js
// app/Support/greeting.js export function greetingFor(name, hour = new Date().getHours()) { const part = hour < 12 ? "morning" : hour < 18 ? "afternoon" : "evening"; return `Good ${part}, ${name || "world"}`; }
NOTE

A function that takes values and returns a value can be used by a route, a controller, a command, and a test without change. A function that takes the request context can only ever be used by a route.

09

Step 6 — give it a model

A model names its table, its primary key, and the fields that may be written. Anything outside fillable is dropped before it reaches the database, which is what keeps a form post from setting a column you never intended.

TERMINAL
npx noderyx make:migration create_greetings --table=greetings npx noderyx make:model Greeting --table=greetings npm run migrate
10

…and using it

The same five methods work on MySQL, PostgreSQL, and MongoDB, so this code does not change if the engine does.

app/Models/Greeting.js
import { Model } from "noderyx-framework"; export class Greeting extends Model { static table = "greetings"; static fillable = ["body"]; } // in a controller action await Greeting.create({ body: greetingFor(name) }); const greetings = await Greeting.all(); return this.render("hello", { name, greetings });
NOTE

Connect once in server.js and hand the adapter to the model: const db = await connect(config.database); Greeting.use(db);

11

Step 7 — run it from the terminal

A command is the same work without a request. It is a plain object with a name, a description, and run(args), and it is found by its signature rather than its filename.

TERMINAL
npx noderyx make:command Greet --signature=hello:greet
12

…and what a command looks like

Because the greeting logic is a plain function, the command imports the same code the controller uses. This is the payoff for keeping helpers free of the request context.

app/Commands/GreetCommand.js
// app/Commands/GreetCommand.js import { greetingFor } from "../Support/greeting.js"; export default { name: "hello:greet", description: "Print a greeting", async run(args) { const [name = "world"] = args; console.log(greetingFor(name)); } };
NOTE

Run it with npx noderyx run hello:greet Ada. Only positional arguments reach run(args)—flags such as --name=Ada are filtered out before your command sees them, so take values as positionals or read process.env.

13

Step 8 — put it on a schedule

Noderyx has no built-in scheduler, and that is deliberate: a process that schedules its own work runs the job twice the moment you run two instances. Use the scheduler your server already has, and point it at the command.

TERMINAL
# Linux or macOS — crontab -e */5 * * * * cd /srv/my-app && /usr/bin/npx noderyx run hello:greet Ada >> /var/log/greet.log 2>&1 # Windows — Task Scheduler schtasks /create /tn "Greet" /tr "npx noderyx run hello:greet Ada" /sc minute /mo 5
NOTE

This is why the work lives in a command rather than a route: cron, systemd timers, Task Scheduler, a container's scheduler, and your deployment platform can all invoke it, and none of them need the web server to be reachable.

14

…or inside the process, carefully

When an external scheduler genuinely is not available, an interval in server.js works—provided you accept its limits and guard it.

  • Gate it behind an environment variable so only one instance runs it.
  • Catch inside the callback—an unhandled rejection in a timer can end the process.
  • unref() lets the process exit instead of being held open by the timer.
  • The schedule restarts whenever the server does, so it is not a substitute for cron on a deploy-often service.
server.js
if (process.env.RUN_SCHEDULER === "true") { const timer = setInterval(async () => { try { await Greeting.create({ body: greetingFor("world") }); } catch (error) { console.error("Scheduled greeting failed:", error.message); } }, 5 * 60 * 1000); timer.unref(); }
15

Where each piece belongs

The whole walkthrough in one table, as a rule of thumb for the next feature.

server.js Routes, services, and startup wiring. Keep handlers thin.
app/Controllers/ Request logic for one resource, once it outgrows a route.
app/Support/ (or any folder) Plain functions with no request context. Reusable and testable.
app/Models/ Table, primary key, and the fillable allowlist.
app/Commands/ Work that runs without a request—imports, reports, cleanups.
app/Middleware/ Behavior that applies across many routes.
database/migrations/ Schema changes, applied in timestamp order.
resources/views/ Noderframe views. They choose what to show, not what to compute.
NOTE

There is no enforced convention for helper folders—app/Support is a suggestion, not a rule the framework knows about.

16

Where to go next

Each step above has a full reference page.

/docs/views The Noderframe language in full: conditions, loops, components.
/docs/routing The request context, middleware, services, and error handling.
/docs/database Migrations, seeders, observers, and the model API.
/docs/commands Every CLI command, including the generators used here.
/functions The complete list of what noderyx-framework exports.