
Security News
Socket Releases Free Certified Patches for Critical vm2 Sandbox Escape
A critical vm2 sandbox escape can allow untrusted JavaScript to break isolation and execute commands on the host Node.js process.
Lightweight Node.js engine for deterministic, intent-driven domain logic orchestration.
π A deterministic, high-performance HTTP engine for modern TypeScript backends.
Itβs not a full framework; itβs the engine
Railiz is the runtime layer for building your own backend architecture.
import { Railiz } from 'railiz'
const app = new Railiz()
app.get('/', (ctx) => {
ctx.json({ hello: 'railiz' })
})
app.run(3000)
π Railiz = control + guarantees
Most Node frameworks make you choose:
railiz removes the trade-off.
Request
β
Context
β
Pipeline (middlewares)
β
Router (linear | hybrid | radix | compiled)
β
Handler
β
Response
π Everything is explicit.
π Nothing is hidden.
npm install railiz
import { Railiz, safeHandler } from 'railiz'
const app = new Railiz()
// Execution order is GUARANTEED
app.pipeline((p) => {
const auth = p.use(authMiddleware)
p.use(rateLimit).after(auth)
})
app.get('/', safeHandler(async (ctx) => {
return {
message: 'Hello Railiz π',
url: ctx.url,
host: ctx.host,
ip: ctx.ip,
headers: ctx.headers,
}
}))
app.get('/page404', (ctx) => {
ctx.throw(404, 'Page not found')
})
app.createServer().listen(3000, () => {
console.log('π Server running at http://localhost:3000')
})
// app.run(3000)
No more middleware order bugs.
app.pipeline((p) => {
const auth = p.use(authMiddleware)
const db = p.use(dbMiddleware)
p.use(cache).before(db)
p.use(rateLimit).after(auth)
})
auth β rateLimit β cache β db
π Execution order is guaranteed.
β No middleware order bugs
β No βwho runs first?β confusion.
| Property / Method | Type / Usage | Description |
|---|---|---|
ctx.params | Record<string, string> | URL parameters, e.g. /users/:id β { id: '123' } |
ctx.query | Record<string, string> | Query string params, e.g. /search?q=hello β { q: 'hello' } |
ctx.data.body | any | Parsed request body (JSON/form) |
ctx.state | any | Per-request storage for middleware or handler |
ctx.json<T>(data: T) | Function | Send JSON response |
ctx.text(text: string) | Function | Send plain text |
ctx.html(html: string) | Function | Send HTML response |
ctx.send(data: any) | Function | Generic send (auto-detect type) |
ctx.statusCode(code: number) | Function | Set HTTP status code |
ctx.set(name: string, value: string) | Function | Set header |
ctx.redirect(url: string, status?: number) | Function | Redirect client |
ctx.sendFile(filePath: string) | Function | Send static file |
ctx.ok<T>(data?: T) | Function | Shortcut 200 OK (JSON) |
ctx.created<T>(data?: T) | Function | Shortcut 201 Created |
ctx.accepted<T>(data?: T) | Function | Shortcut 202 Accepted |
ctx.noContent() | Function | Shortcut 204 No Content |
ctx.badRequest(msg?: string) | Function | Shortcut 400 Bad Request |
ctx.unauthorized(msg?: string) | Function | Shortcut 401 Unauthorized |
ctx.forbidden(msg?: string) | Function | Shortcut 403 Forbidden |
ctx.notFound(msg?: string) | Function | Shortcut 404 Not Found |
ctx.internalError(msg?: string) | Function | Shortcut 500 Internal Server Error |
π Inspired by Koa, but stricter and typed.
β οΈ TTL and expiration values are always in milliseconds (ms) Example:
60000 = 60 seconds
Railiz uses a multi-stage routing engine that automatically adapts based on the number of registered routes.
| Mode | Engine | When used |
|---|---|---|
| linear | simple sequential scan | < 100 routes |
| hybrid | indexed + LRU cache | < 1000 routes |
| compiled | precompiled matcher | > 1000 routes |
Routing is designed to be explicit, predictable, and REST-friendly. Wildcards (*) are only allowed for middleware, fallback, or proxy routes, and should be avoided in core API endpoints to prevent ambiguity.
const app = new Railiz() // auto mode (recommended)
Or explicitly:
const app = new Railiz({ router: 'linear' })
Available modes:
| Type | Path | Example |
|---|---|---|
| Static | /users | List all users |
| Param | /users/:id | Get user by ID |
const app = new Railiz()
app.route({ method: 'GET', path: '/users/:id', middleware: [auth], handler: async (ctx) => { return ctx.ok({ id: ctx.params.id }) }, })
Railiz automatically upgrades its routing engine as the application scales:
1000 routes β compiled router (precomputed match functions for maximum throughput)
basic
app.group('/api', (r) => {
r.get('/health', ctx => ctx.ok())
})
with middleware
app.group('/admin', [authMiddleware], (r) => {
r.get('/dashboard', (ctx) => ctx.ok({ message: 'Admin dashboard' }))
})
nested
app.group('/api', (r) => {
r.group('/v1', (r2) => {
r2.get('/status', (ctx) => ctx.ok({ status: 'ok' }))
})
})
app.plugin((app) => {
app.use(async (ctx, next) => {
console.log('Custom plugin triggered', ctx.path)
await next?.()
})
})
You can create your own plugins for purposes such as:
π‘ Notes:
app.use(async (ctx, next) => {
console.log(ctx.path)
await next()
})
app.route({
method: 'GET',
path: '/boom',
handler: async () => {
throw new Error('boom')
},
errorBoundary: async (ctx) => {
ctx.status = 500
ctx.body = 'Handled error'
},
})
| Case | Use |
|---|---|
| 4xx client error | β HttpError |
| 5xx system error | β normal Error |
| validation | β HttpError(400) |
| auth | β HttpError(401/403) |
if (!ctx.user) {
throw new HttpError(401, 'Unauthorized')
}
throw new HttpError(422, 'Validation failed', {
field: 'email',
reason: 'invalid format',
})
Built-in Presets:
Example with Presets & Dependencies:
import { Railiz } from 'railiz'
import { apiPreset, loggerPreset, authPreset, applyPresets } from 'railiz'
const app = new Railiz({ trace: true })
const presets = [
apiPreset({ cors: true }),
loggerPreset({ enabled: true }),
authPreset({ strategy: 'jwt' }),
]
// Example: authPreset depends on apiPreset
presets.find(p => p.name === 'auth').dependsOn = ['api']
applyPresets(app, presets)
app.get('/profile', (ctx) => {
ctx.ok({ user: ctx.state.user })
})
app.run(3000)
app.on('request', (ctx) => console.log('Incoming request', ctx.path))
app.on('response', (ctx) => console.log('Response status', ctx.status))
app.on('error', (ctx, err) => console.error('Error:', err.message))
const { app } = createApp({
// railizOptions: {
// router: 'radix',
// },
presets: [
loggerPreset({ enabled: true }),
],
debug: true,
onCreate() {
console.log('π create')
},
onReady() {
console.log('β
ready')
},
})
app.get('/hello', (ctx) => {
return ctx.ok({ message: 'Hello Railiz' })
})
app.run(3000)
// NODE_ENV != production => π Server running at http://localhost:3000
Build your own opinionated backend in seconds.
Railiz allows running tasks after the response has been sent:
app.get('/send-email', async (ctx) => {
// Respond immediately
ctx.ok({ message: 'Email will be sent in background' })
// Push background task
ctx.backgroundTasks!.push(async () => {
await sendEmail(ctx.query.to, 'Hello from Railiz!')
console.log(`β
Email sent to ${ctx.query.to}`)
})
})
ctx.backgroundTasks!.push(task1, task2, task3)
app.inject('db', () => new Database(), 'singleton')
app.inject('requestId', () => crypto.randomUUID(), 'scoped')
app.inject('cache', () => new Map(), 'transient')
app.get('/test', (ctx) => {
const db = ctx.di.resolve('db')
const user = await db.user.findMany()
const requestId = ctx.di.resolve('requestId')
return ctx.json({ requestId })
})
| Scope | Behavior |
|---|---|
| singleton | 1 instance app |
| scoped | 1 instance / request |
| transient | always create |
scoped = per request context
app.use(expressAdapter(app))
export const handler = lambdaAdapter(app)
export default workersAdapter(app)
Bun.serve(bunAdapter(app))
fastify.all('*', fastifyAdapter(app))
app.use(errorHandler())
| Middleware / Feature | Node.js | Lambda | Edge | Notes |
|---|---|---|---|---|
serveStatic | β | β | β | Static assets (early exit before pipeline) |
logger | β | β | β | Logs request lifecycle |
normalizeHeaders | β οΈ | β οΈ | β οΈ | Normalizes incoming headers |
cors | β | β | β | CORS policy |
helmet | β | β | β οΈ | Security headers |
json parser | π | π | β οΈ | Parses JSON body |
bodyParser | π | β | β | Parses form/urlencoded bodies |
queryParser | β | β | β | Parses query string |
jwtAuth | β | β | β οΈ | Authentication layer |
validateDynamic | β | β | β | Request validation (headers/body/transform) |
rateLimit | β | β | β | Traffic control (anti-abuse protection) |
timeout | β | β | β | Aborts slow requests |
cache (memory) | β | β | β | Local cache (not distributed-safe) |
httpResponseCache | β | β | β | Full HTTP response caching |
session | β οΈ | β | β | Stateful session storage |
cookies | β οΈ | β οΈ | β οΈ | Cookie handling |
retry | β οΈ | β οΈ | β οΈ | Retry transient failures |
redirect | β οΈ | β οΈ | β οΈ | Redirect handling |
buffer | β | β | β | Response buffering layer |
circuit-breaker | β οΈ | β οΈ | β οΈ | Prevents cascading failures |
dedupe | β οΈ | β οΈ | β οΈ | Single-flight request deduplication |
content-negotiation | β | β | β οΈ | Response format negotiation |
metrics | β οΈ | β οΈ | β οΈ | Observability (latency, errors, counts) |
errorHandler | β | β | β | Global error boundary (MUST be last) |
β
Fully supported.
β οΈ Works with limitations.
π Requires hybrid/adapter implementation.
β Not suitable for runtime.
Middleware should be applied in order from inbound (security + parsing) β authentication + validation β traffic control β cache + business logic β resilience β response β error handler at the end.
import {
Railiz,
json,
bodyParser,
logger,
cors,
rateLimit,
cache,
cookies,
httpResponseCache,
helmet,
validateDynamic,
timeout,
retry,
redirect,
buffer,
errorHandler,
serveStatic
} from 'railiz'
const app = new Railiz()
app.use(serveStatic('./public'))
// serves static files (HTML, CSS, JS) with early exit
app.use(logger())
// logs incoming requests + responses
app.use(cors({ origin: '*' }))
// enables cross-origin requests
app.use(helmet())
// adds security headers (XSS, clickjacking protection)
// --------------------
// Parsing layer
// --------------------
app.use(json())
// parses JSON request body
app.use(bodyParser({ json: { limit: '2mb' } }))
// parses form/urlencoded + enforces body size limit
// --------------------
// Auth layer (MUST be here)
// --------------------
app.use(jwtAuth('mysecret', { mandatory: true }))
// verifies JWT and blocks unauthorized requests
// --------------------
// Traffic control
// --------------------
app.use(rateLimit({ limit: 100, windowMs: 60_000 }))
// prevents abuse by limiting requests per time window
app.use(timeout({ response: 5000, socket: 10000 }))
// aborts slow/hanging requests
// --------------------
// Cache layer
// --------------------
app.use(cache({ ttl: 30_000 }))
// generic cache for data/service-level results
app.use(httpResponseCache({ ttl: 60 }))
// caches full HTTP responses per request key
// --------------------
// Reliability layer
// --------------------
app.use(retry({ limit: 2, interval: 100 }))
// retries failed transient operations
app.use(redirect({ limit: 3, sameHost: true }))
// handles redirects safely with limits
// --------------------
// Response processing
// --------------------
app.use(cookies({ ttl: 60 * 60 * 1000 }))
// manages cookie storage and sending
app.use(buffer())
// buffers response for post-processing
// --------------------
// Error boundary (ALWAYS LAST)
// --------------------
app.use(errorHandler())
// catches all errors and prevents crash
// ------------------------
// Routes
// ------------------------
// Simple GET
app.get('/ping', (ctx) => ctx.ok({ message: 'pong' }))
// POST with validation
app.post(
'/users',
validateDynamic({
body: (data) => {
if (!data.name) return 'Missing name'
if (!data.age || typeof data.age !== 'number') return 'Invalid age'
return true
},
}),
(ctx) => {
if (ctx.response.validate?.body) {
return ctx.badRequest(ctx.response.validate.body)
}
const id = Date.now()
return ctx.created({ id, ...ctx.data.body })
}
)
// Route with redirect
app.get('/old-route', (ctx) => ctx.redirect('/new-route'))
app.get('/new-route', (ctx) => ctx.ok({ message: 'You are redirected here!' }))
// POST that sets cookies
app.post('/login', (ctx) => {
const { username } = ctx.data.body || {}
if (!username) return ctx.badRequest('Missing username')
ctx.response!.headers = ctx.response!.headers || {}
ctx.response!.headers['Set-Cookie'] = [`user=${username}; HttpOnly; Max-Age=3600`]
return ctx.ok({ message: `Welcome ${username}` })
})
// GET route that reads cookies
app.get('/me', (ctx) => {
const cookies = ctx.req.headers['cookie'] || ''
return ctx.ok({ cookies })
})
// Fallback route
app.get('/*', (ctx) => ctx.notFound('Route not found'))
// ------------------------
// Run server
// ------------------------
app.run(3000)
console.log('π Railiz server running on http://localhost:3000')
security β parse β auth β rate limit β cache β business β response β error
This plugin automatically generates OpenAPI 3 documentation for your Railiz app and can optionally mock API responses for rapid development, limitation (type inference partial / runtime metadata required)
import { Railiz, openApi } from 'railiz'
const app = new Railiz({ router: 'linear' })
// Register plugin with mock responses
app.plugin(openApi({ title: 'My API', version: '1.0.0', mock: true }))
// Define routes
app.get('/users/:id', async (ctx) => {
ctx.json({ success: true, userId: ctx.params.id })
})
app.post('/login', async (ctx) => {
ctx.json({ success: true, token: 'fake-jwt-token' })
})
// Run server
app.run(3000)
Visit http://localhost:3000/openapi.json to see your API documentation.
When mock: true is enabled, routes return fake data based on their response schema, allowing frontend teams to start development before the backend is fully implemented.
Railiz provides a 3-layer caching model:
L1 (memo) β L2 (requestCache) β Plugin Cache β Origin (DB/API)
When cache layers are combined, resolution order is:
Use case:
app.get('/users/:id', async (ctx) => {
const user = await ctx.memo(
{ id: ctx.params.id },
() =>
ctx.requestCache(
{ route: 'user', id: ctx.params.id },
() => getUser(ctx.params.id),
{ ttl: 30_000 },
),
{ ttl: 5_000 },
)
return ctx.json({
cache: ctx.state.cacheHit ?? 'MISS',
user,
})
})
Use case:
Use case:
app.get('/users/:id', async (ctx) => {
const user = await ctx.requestCache(
{ route: 'user', id: ctx.params.id },
() => getUser(ctx.params.id),
{
ttl: 30000, debug: true,
// force: true,
// FORCE REFRESH (bypass cache completely)
}
)
return ctx.json({
source: 'L2 requestCache',
cache: ctx.state.cacheHit ?? 'MISS',
user,
})
})
app.inject('cacheClient', new MemoryCache())
app.plugin(cachePlugin([new MemoryCache()]))
inject > plugin
Protect your system from cascading failures by short-circuiting unstable services.
import { circuitBreaker } from 'railiz'
app.use(
circuitBreaker({
failureThreshold: 5,
resetTimeout: 30_000, // ms
})
)
app.use(
circuitBreaker({
key: (ctx) => {
if (ctx.path.startsWith('/payments')) return 'payment-service'
if (ctx.path.startsWith('/users')) return 'user-service'
return 'default'
},
})
)
β οΈ IMPORTANT:
If you don't provide a
key, the circuit state is GLOBAL. One failing route can block all others. Usekeyto isolate circuits per service or dependency.
Example with external API
app.get(
'/payments/:id',
circuitBreaker({
key: () => 'payment-api',
failureThreshold: 3,
resetTimeout: 10_000,
}),
async (ctx) => {
const data = await fetchPayment(ctx.params.id) // external call
ctx.ok(data)
}
)
| State | Description |
|---|---|
| CLOSED | Normal operation |
| OPEN | Requests are blocked (fast fail) |
| HALF_OPEN | Allows 1 request to test recovery |
CLOSED --(fail x N)--> OPEN
OPEN --(after resetTimeout)--> HALF_OPEN
HALF_OPEN --(success)--> CLOSED
HALF_OPEN --(fail)--> OPEN
// HTTP Status: 503
{
"message": "Service unavailable (circuit open: payment-api)"
}
// β BAD (global breaker)
app.use(circuitBreaker())
// β
GOOD (isolated per dependency)
app.use(
circuitBreaker({
key: (ctx) => ctx.path.startsWith('/payments')
? 'payment-api'
: 'default'
})
)
Always prefer per dependency, not global
Node HTTP
β
Railiz Core
β
Router (Radix / Linear)
β
Context
β
Handlers
railiz is the only one here that gives you deterministic middleware execution.
| Criteria | Railiz | Express.js | Fastify | NestJS |
|---|---|---|---|---|
| Core concept | Execution engine | Minimal framework | Web framework | Full framework |
| Abstraction level | π₯ Low (full control) | Low | Medium | High |
| Middleware model | β deterministic | β implicit order | β οΈ plugin-based | β οΈ decorator-based |
| Routing performance | β‘ Radix O(k) | β Linear scan | β‘ Optimized | β‘ (Fastify under hood) |
| Type safety | β strong | β weak | β strong | β strong |
| Architecture | β flexible core | β unstructured | β οΈ opinionated | β οΈ enforced patterns |
| Plugin system | π simple & explicit | β οΈ ad-hoc | β rich | β DI-based |
| Boilerplate | πͺΆ minimal | πͺΆ minimal | β οΈ medium | β high |
| Learning curve | π’ low | π’ low | π‘ medium | π΄ high |
| Use case | Engine / custom arch | Small apps / legacy | APIs / services | Enterprise apps |
π‘ Takeaways:
π Railiz is for engineers who want control.
You control:
- architecture
- data
- plugins
railiz controls:
- execution
- routing
- lifecycle
MIT
FAQs
Lightweight Node.js engine for deterministic, intent-driven domain logic orchestration.
The npm package railiz receives a total of 60 weekly downloads. As such, railiz popularity was classified as not popular.
We found that railiz demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
A critical vm2 sandbox escape can allow untrusted JavaScript to break isolation and execute commands on the host Node.js process.

Research
Five malicious NuGet packages impersonate Chinese .NET libraries to deploy a stealer targeting browser credentials, crypto wallets, SSH keys, and local files.

Security News
pnpm 11 turns on a 1-day Minimum Release Age and blocks exotic subdeps by default, adding safeguards against fast-moving supply chain attacks.