@vernali/core
v0.2.0
Published
Core functionality for Vernali framework. POC for now, will be expanded in the future.
Readme
@vernali/core
Core runtime for the Vernali framework. Provides a minimal, composable HTTP layer built directly on top of Node.js native http primitives, without any third-party runtime dependencies.
Note:
@vernali/contractsis a dependency of this package and provides the shared interface types.@vernali/coreis designed as a minimalist middleware layer — lightweight by intent, with no runtime dependencies beyond Node.js itself.
Requirements
- Node.js ≥ 18 (native ESM support required)
- TypeScript ≥ 6.x (for type-checked usage)
Installation
npm install @vernali/corePeer dependencies (optional, required only if using the view rendering engine):
npm install ejs # for EJS template support
npm install pug # for Pug template supportArchitecture Overview
@vernali/core follows a layered middleware composition pattern. Each incoming HTTP request is processed through a pipeline:
IncomingMessage + ServerResponse
│
▼
Application.handleRequest()
│
├── loadContext() ← body parsing (POST/PUT/PATCH)
│
├── dispatch() ← router.handle(ctx)
│ │
│ └── Router.#dispatch()
│ │
│ └── Layer.matches() → compose(handlers)(ctx, next)
│
├── handleNotFound() ← 404 fallback
│
└── handleError() ← 500 error boundaryThe Router uses a Layer stack where each Layer encapsulates a path pattern, an optional HTTP method, and an ordered list of handlers. Path matching supports static paths, named parameters (:param), wildcard (*), and RegExp patterns.
API Reference
Application
The central application class. Exposes the full routing DSL and the ready() callback for integration with a native http.Server.
import { Application } from '@vernali/core'
import { createServer } from 'node:http'
const app = new Application()
app.get('/health', (ctx) => {
ctx.json({ status: 'ok' })
})
createServer(app.ready()).listen(3000)Constructor options
new Application(options?: {
dispatch?: (ctx: Context) => Promise<void>
onNotFound?: (ctx: Context) => Promise<void>
onError?: (err: unknown, ctx: Context) => Promise<void>
})All three hooks are optional and override the built-in default behaviors:
| Option | Default behavior |
|---|---|
| dispatch | Routes request through the internal Router |
| onNotFound | Returns 404 Not Found as plain text |
| onError | Logs to console.error, returns 500 Internal Server Error |
Routing methods
All routing methods return this for chaining and accept one or more handler functions.
app.get(path, ...handlers)
app.post(path, ...handlers)
app.put(path, ...handlers)
app.patch(path, ...handlers)
app.delete(path, ...handlers)
app.all(path, ...handlers) // matches all HTTP methods
app.use([path], ...handlers) // mounts middleware or sub-routerspath accepts: string (static or parameterized), RegExp, or '*' (wildcard).
app.use() — Mounting sub-routers
import { Application, Router } from '@vernali/core'
const api = new Router()
api.get('/users', listUsers)
api.post('/users', createUser)
const app = new Application()
app.use('/api/v1', api)app.setViews(engine, viewsDir)
Configures the template engine and the views directory for ctx.render().
app.setViews('ejs', './views')
app.setViews('pug', './templates')app.serveStatic(rootDir, options?)
Returns a middleware that serves static assets from rootDir. Handles directory index resolution (index.html by default), MIME type detection, path traversal protection, and HEAD requests.
app.use(app.serveStatic('./public'))
// or scoped to a prefix:
app.use('/static', app.serveStatic('./assets'))app.ready()
Returns a (req, res) => void callback suitable for http.createServer().
Router
A standalone, nestable router. Shares the same routing DSL as Application.
import { Router } from '@vernali/core'
// or via the named export:
import { Router } from '@vernali/core/router'
const router = new Router()
router.get('/items', listItems)
router.get('/items/:id', getItem)
router.post('/items', createItem)
router.delete('/items/:id', deleteItem)Routers can be nested to arbitrary depth via app.use() or router.use().
Context
The request/response abstraction passed to every handler. Wraps IncomingMessage and ServerResponse and provides a unified API surface.
Properties
| Property | Type | Description |
|---|---|---|
| req | IncomingMessage | Raw Node.js request object |
| res | ServerResponse | Raw Node.js response object |
| method | string | HTTP method in uppercase |
| path | string | Current path segment being matched (mutated during sub-router dispatch) |
| originalUrl | string | Unmodified request URL |
| baseUrl | string | Accumulated matched prefix |
| params | Record<string, string> | Named route parameters (e.g. :id) |
| query | Record<string, string> | Parsed query string values |
| searchParams | URLSearchParams | Raw URLSearchParams instance |
| body | Record<string, any> | Parsed JSON body (populated after body parsing middleware) |
| state | Record<string, any> | Arbitrary per-request shared state |
| cookies | Record<string, string> | Parsed cookies (populated after cookieParser middleware) |
| headers | Record<string, string \| string[]> | Request headers |
| requestId | string | Assignable request identifier for tracing |
| closed | boolean | Indicates if the response has been terminated |
Response methods
ctx.status(code: number): this // sets res.statusCode, chainable
ctx.json(data: Record<string, any>): void // sends JSON response
ctx.send(data: string): void // sends plain text response
ctx.html(content: string): void // sends HTML response
ctx.redirect(url: string, code?: number): void // 302 by default
ctx.setHeader(key: string, value: string): this // chainable header setter
ctx.render(view, data?, options?): Promise<void> // renders a templateExample — chained status + JSON:
app.get('/error', (ctx) => {
ctx.status(422).json({ error: 'Unprocessable Entity' })
})Built-in Middleware
bodyParser
Parses JSON request bodies for POST, PUT, and PATCH requests. Invoked automatically by Application.loadContext().
import { bodyParser } from '@vernali/core'
// Auto-applied by Application. For manual use:
app.use(async (ctx, next) => {
ctx.body = await bodyParser(ctx)
await next()
})cookieParser
Parses the Cookie header and populates ctx.cookies.
import { cookieParser } from '@vernali/core'
app.use(cookieParser)
app.get('/profile', (ctx) => {
const token = ctx.cookies['auth_token']
// ...
})Path Matching Reference
| Pattern | Matches |
|---|---|
| /users | Exact path /users |
| /users/:id | /users/123, exposes ctx.params.id |
| /files/* (wildcard) | Any path under /files/* |
| RegExp | Custom expression evaluated against ctx.path |
| '/' in use() | Matches any path (prefix-based) |
For use() mounts, matching is prefix-based — the consumed prefix is stripped from ctx.path before the handler or sub-router is invoked. For route methods (get, post, etc.), matching is exact.
View Rendering
ctx.render() supports three engines:
| Engine | Package | Template extension |
|---|---|---|
| html | built-in | .html |
| ejs | ejs (peer dep) | .ejs |
| pug | pug (peer dep) | .pug |
app.setViews('ejs', './views')
app.get('/page', async (ctx) => {
await ctx.render('home', { title: 'Home', user: ctx.state.user })
})The built-in HTML engine supports {{ variable }} interpolation with automatic HTML escaping. Nested property access is supported via dot notation ({{ user.name }}).
Package Exports
The package exposes two public entry points:
| Specifier | Entry point | Description |
|---|---|---|
| @vernali/core | dist/index.js | Main exports: Application, Context, Router, bodyParser, cookieParser and types |
| @vernali/core/router | dist/lib/router/index.js | Standalone Router export |
Development Scripts
npm run build # clean + tsc compile to ./dist
npm run type-check # tsc --noEmit (no output, type validation only)
npm run dev # watch mode via nodemon
npm run lint # StandardJS lint
npm run lint:fix # StandardJS lint with auto-fixType Contracts
All public interfaces are defined in @vernali/contracts and re-exported where appropriate. The types enforced in this package include:
IApplication<Ctx>— implemented byApplicationIRouter<Ctx>— implemented byRouterILayer<Ctx>— implemented byLayerIMiddlewareFn<Ctx>— the middleware function signature(ctx, next?) => Promise<void>IContext— base context interfaceIBodyParser— body parsing contractIServerOptions,ILogger— server and logging interfaces
License
ISC © Yael De Jesus
