Skip to content

@nodalite/core

The foundation of Nodalite. Zero runtime dependencies.

Also available as the unscoped nodalite package — identical exports, zero overhead.

bash
npm install nodalite
# or
npm install @nodalite/core
ts
import { App } from 'nodalite';
// or
import { App } from '@nodalite/core';

App

The main application class. Configures routes, middleware, and error handling.

ts
import { App } from '@nodalite/core';

const app = new App({ name: 'my-api' });

Options

OptionTypeDefaultDescription
namestring"nodalite-app"Service name used in error logs

Methods

use(pathOrMiddleware, middleware?)

Register global middleware. If pathOrMiddleware is a string, the middleware only applies to matching paths.

ts
app.use('*', cors());                          // all paths
app.use('/api/*', authMiddleware);             // prefixed
app.use(logger());                             // shorthand for '*'

get | post | put | patch | delete | query | all(path, handler, middlewares?)

Register a route handler with optional route-scoped middleware.

ts
app.get('/users/:id', handler);
app.post('/users', validate(schema), handler);

query(path, handler, middlewares?)

Register a handler for the QUERY HTTP method (RFC 10008). QUERY is safe and idempotent like GET, but supports a request body — useful for search or filter operations that are too complex for query strings.

ts
app.query('/search', async (c) => {
  const { filters } = await c.req.json();
  const results = await db.search(filters);
  return c.json({ results });
});

on(method, path, handler, middlewares?)

Register a route with a custom HTTP method.

group(prefix, build)

Group routes under a shared prefix.

ts
app.group('/api/v1', (g) => {
  g.use(auth);
  g.get('/users', list);
});

onError(handler)

Override the default error handler. Receives (err, c).

notFound(handler)

Override the default 404 handler.

handle(request, platform?)

The single entrypoint. Adapters call this. Returns Promise<Response>.

fetch(request, platform?)

Alias for handle, matching the Fetch API convention used by Bun, Deno, and Workers.

Context

The object every middleware and handler receives.

ts
import { Context } from '@nodalite/core';

Properties

PropertyTypeDescription
c.reqRequestFacadeTyped request wrapper
c.platformRecord<string, unknown>Adapter-supplied platform info

Methods

MethodDescription
c.set(key, value)Store a value for the request lifetime
c.get(key)Retrieve a stored value
c.header(name, value)Queue a response header
c.status(code)Set the response status code
c.json(data, init?)Send JSON response
c.text(data, init?)Send plain text response
c.html(data, init?)Send HTML response
c.redirect(location, status?)Redirect (default 302)
c.noContent()Send 204 No Content
c.stream(body, init?)Send a streaming response

RequestFacade (c.req)

MethodDescription
c.req.param(name)Typed route param (/users/:idparam('id'))
c.req.query(name)Query string parameter
c.req.queryAll(name)All values for a query parameter
c.req.header(name)Request header value
c.req.json()Parsed JSON body (cached)
c.req.text()Raw text body (cached)
c.req.formData()Form data body
c.req.arrayBuffer()Binary body
c.req.bodyStreamRaw ReadableStream for piping

HttpError

Standardized HTTP errors that the framework handles automatically.

ts
import { HttpError, isHttpError } from '@nodalite/core';

throw HttpError.notFound('User not found');
throw HttpError.badRequest('Invalid input');
throw HttpError.unauthorized('Login required');
throw HttpError.forbidden('Not allowed');
throw HttpError.conflict('Already exists');
throw HttpError.requestTimeout('Timed out');          // 408
throw HttpError.unsupportedMediaType('Bad type');     // 415
throw HttpError.internal('Unexpected error'); // defaults to non-exposed
throw new HttpError(418, "I'm a teapot", { expose: true });

Properties

PropertyTypeDescription
statusnumberHTTP status code
messagestringHuman-readable message
exposebooleanWhether to expose details to the client

compose

The middleware composition utility. Used internally by App, but also exported for advanced use cases.

ts
import { compose } from '@nodalite/core';

const chain = compose([middleware1, middleware2], finalHandler);
await chain(context);

validate

Standard-Schema-based request body validation. Works with Zod 3.24+, Valibot, ArkType, etc.

ts
import { validate } from '@nodalite/core';
import { z } from 'zod';

const schema = z.object({ name: z.string() });

app.post('/users', validate(schema), (c) => {
  const { name } = c.req.params; // validated and typed
  return c.json({ name });
});

On validation failure, returns a 400 response with structured issues.

discover

Auto-discovers route files from a directory and registers them on the app. Each route file should export a default function that receives the app (or a RouteGroup when inside a prefix directory):

ts
// routes/users.ts
import type { App } from 'nodalite';
export default (app: App) => {
  app.get('/users', (c) => c.json({ users: [] }));
  app.post('/users', async (c) => { ... });
};
ts
import { App } from 'nodalite';
import { discover } from '@nodalite/core';

const app = new App();
await discover(app, './routes');

Subdirectories become route groups with automatic prefix detection via _prefix.ts files:

routes/
  users.ts          -> app.get("/users", ...)
  posts/
    _prefix.ts      -> export default "/posts"
    index.ts        -> app.get("/", ...)
    comments.ts     -> app.get("/comments", ...)

_prefix.ts should call app.use() with the prefix string — discover captures the first argument:

ts
// routes/posts/_prefix.ts
import type { App } from 'nodalite';
export default (app: App) => {
  app.use('/posts');
};

Signature

ts
discover(app: App, dir: string, options?: DiscoverOptions): Promise<void>

DiscoverOptions

OptionTypeDefaultDescription
dirstringDirectory to scan (relative to cwd)
extensionsstring[]['.ts', '.js', '.mts', '.mjs']File extensions to include
useDirectoryPrefixbooleantrueUse directory names as route prefixes
prefixFilestring'_prefix'Filename pattern for prefix definition files

WARNING

Auto-discovery uses dynamic import(), which works on Node, Bun, and Deno. For Cloudflare Workers or other bundled runtimes, use static imports or a build-time generation step instead.

Released under the MIT License.