NODERYX
FRAMEWORK v0.7.1

GETTING STARTED

BUILDING

PRODUCTION

DEVICES

REFERENCE

NODERYX / DOCS / BUILDING / 04
BUILDING / 04

Packages

Build reusable Noderyx extensions with ordinary JavaScript modules and npm conventions—no plugin runtime, no proprietary format.

01

What a package is

A Noderyx package is an ES module that exports a provider. It can register routes, middleware, error handling, and services, and it can run startup work—without changing the framework core or your application's source files.

  • Local packages live under packages/ and load automatically.
  • Published packages install from npm and are enabled explicitly in noderyx.config.js.
  • Both use the same provider shape, so a local package can be published unchanged.
  • Packages use the public framework API only, which is what keeps them working across releases.
02

Create a local package

The generator writes a complete, runnable package, including an example route.

TERMINAL
npx noderyx make:package hello-world packages/ hello-world/ package.json README.md src/ index.js
NOTE

Restart the development server afterwards, then open http://localhost:3000/hello-world.

03

The provider

definePackage() validates the provider early—so a typo fails at startup with a clear message—and returns it unchanged.

packages/hello-world/src/index.js
import { definePackage } from "noderyx-framework"; export default definePackage({ name: "hello-world", register({ app, options }) { app.get("/hello", ({ json }) => json({ message: options.message ?? "Hello from the package" })); }, async boot({ name }) { console.log(`Loaded ${name}`); } });
04

Lifecycle

A provider has two optional methods. register() adds routes, middleware, and error handling; boot() runs after every enabled package has registered, which is where cross-package work belongs. Both may be asynchronous.

  • Registration happens in configured order; local packages are discovered before configured ones run.
  • Booting happens afterwards, in the same order.
  • Anything that needs another package to exist belongs in boot(), not register().
05

The lifecycle context

Both methods receive the same object.

app The active application instance.
name The provider's package name.
options Application-specific options from noderyx.config.js.
config The full Noderyx configuration.
root Absolute path to the application root.
06

Register routes

Packages use exactly the same routing methods as applications. Prefix your paths so two packages cannot collide, and so an application can predict what a package added.

src/index.js
register({ app, options }) { const prefix = options.routePrefix ?? "/reports"; app.get(prefix, ({ json }) => json({ reports: [] })); app.post(prefix, ({ body, json }) => json(body, 201)); app.put(`${prefix}/:id`, ({ params, json }) => json({ id: params.id })); app.delete(`${prefix}/:id`, ({ params, json }) => json({ deleted: params.id })); }
NOTE

Application routes are registered before loadPackages(), so a user route always wins over a package route on the same path.

07

Register middleware and services

Middleware from a package behaves like any other middleware. Services registered with app.provide() become available to the whole application through service(name).

src/index.js
register({ app }) { app.use(async (context, next) => { const started = Date.now(); await next(); console.log(`${context.request.method} completed in ${Date.now() - started}ms`); }); app.provide("reports", createReportClient()); }
NOTE

Do not change global prototypes or reach into undocumented framework internals—that is what breaks on the next release.

08

Package options

Local packages load with an empty options object. Configured packages receive whatever the application passes, and a provider should validate what it depends on.

noderyx.config.js
// noderyx.config.js export default { packages: [ { package: "@acme/noderyx-reports", options: { routePrefix: "/reports", pageSize: 25 } } ] };
09

…and validating them

Fail loudly at startup rather than producing a confusing 404 later.

src/index.js
register({ app, options }) { const prefix = options.routePrefix ?? "/reports"; if (!prefix.startsWith("/")) throw new Error("routePrefix must begin with /"); app.get(prefix, ({ json }) => json({ pageSize: options.pageSize ?? 20 })); }
10

Enable, disable, and control discovery

A configured entry can be switched off without removing it, and automatic discovery of packages/ can be disabled or pointed at a different directory.

JAVASCRIPT
packages: [ { package: "@acme/noderyx-reports", enabled: false } ] await loadPackages(app, config.packages, { config, discover: false, directory: "extensions" });
NOTE

Explicit activation is deliberate: an unrelated dependency in node_modules never runs startup code merely because it is installed.

11

Install a published package

Install with npm, then enable it. Two steps, both visible in your repository history.

TERMINAL
npm install @acme/noderyx-reports
NOTE

Then add it to the packages array: packages: ["@acme/noderyx-reports"].

12

Prepare a package for npm

Change the generated manifest from private local development to a publishable package. Keep noderyx-framework as a peer dependency so the application owns one framework version instead of installing a second copy inside the package.

packages/reports/package.json
{ "name": "@acme/noderyx-reports", "version": "1.0.0", "private": false, "type": "module", "main": "src/index.js", "files": ["src", "README.md", "LICENSE"], "peerDependencies": { "noderyx-framework": ">=0.1.0 <2" }, "keywords": ["noderyx", "noderyx-package"] }
13

Publish

Preview the archive before publishing so no stray build output ships with it.

TERMINAL
npm pack --dry-run npm publish --access public
14

Test a package

Test providers against a real application with Node's built-in test runner. No framework-specific harness is required.

packages/hello-world/test/provider.test.js
import test from "node:test"; import assert from "node:assert/strict"; import { loadPackages, noderyx } from "noderyx-framework"; import provider from "../src/index.js"; test("registers the package", async () => { const app = noderyx({ views: "./test/fixtures/views" }); const loaded = await loadPackages(app, [provider], { discover: false }); assert.equal(loaded[0].name, "hello-world"); });
NOTE

Also cover HTTP behavior, invalid options, and startup failures—the three things that break applications downstream.

15

Compatibility and versioning

A package is a promise to somebody else's application. These rules keep that promise cheap to honour.

  • Follow semantic versioning, and declare the tested framework range in peerDependencies.
  • Import only from noderyx-framework—never from files inside noderyx-framework/framework/.
  • Treat removed routes, renamed options, and changed responses as breaking changes.
  • Run the application's tests after package and framework updates.
  • Never rewrite application source automatically from a provider.