πŸš€ Big News:Socket Has Acquired Secure Annex.Learn More β†’
Socket
Book a DemoSign in
Socket

railiz

Package Overview
Dependencies
Maintainers
1
Versions
69
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

railiz

Lightweight Node.js engine for deterministic, intent-driven domain logic orchestration.

latest
Source
npmnpm
Version
0.5.0
Version published
Weekly downloads
460
-31.14%
Maintainers
1
Weekly downloads
Β 
Created
Source

🌐 railiz

NPM Downloads Bundle Size

LIVE EXAMPLE

πŸš€ 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.

Basic

import { Railiz } from 'railiz'

const app = new Railiz()

app.get('/', (ctx) => {
  ctx.json({ hello: 'railiz' })
})

app.run(3000)

What is Railiz (in 10 seconds)

  • Not a framework β†’ no opinions forced
  • Not a router β†’ more than routing
  • Not just middleware β†’ execution engine

πŸ‘‰ Railiz = control + guarantees

Why railiz?

Most Node frameworks make you choose:

  • Flexibility β†’ chaos (Express)
  • Speed β†’ constraints (Fastify)
  • Structure β†’ heaviness (NestJS)

railiz removes the trade-off.

Core Features

  • ⚑ Radix-tree routing (O(k))
  • 🧠 Deterministic middleware execution (no order bugs)
  • 🧩 Pipeline-based orchestration (not just chain)
  • πŸ”Œ Plugin-first architecture
  • πŸͺΆ Ultra-lightweight core
  • πŸ“ Full TypeScript inference (params, context)
  • 🌐 Framework-agnostic (works anywhere)

Build with railiz?

  • REST APIs (faster & safer than Express)
  • Custom backend frameworks (like NestJS, but yours)
  • High-performance microservices
  • Edge / serverless handlers

Mental Model

Request
  ↓
Context
  ↓
Pipeline (middlewares)
  ↓
Router (linear | hybrid | radix | compiled)
  ↓
Handler
  ↓
Response

πŸ‘‰ Everything is explicit.
πŸ‘‰ Nothing is hidden.

Installation

npm install railiz

Quick example

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)

Deterministic Execution

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.

Context API

Property / MethodType / UsageDescription
ctx.paramsRecord<string, string>URL parameters, e.g. /users/:id β†’ { id: '123' }
ctx.queryRecord<string, string>Query string params, e.g. /search?q=hello β†’ { q: 'hello' }
ctx.data.bodyanyParsed request body (JSON/form)
ctx.stateanyPer-request storage for middleware or handler
ctx.json<T>(data: T)FunctionSend JSON response
ctx.text(text: string)FunctionSend plain text
ctx.html(html: string)FunctionSend HTML response
ctx.send(data: any)FunctionGeneric send (auto-detect type)
ctx.statusCode(code: number)FunctionSet HTTP status code
ctx.set(name: string, value: string)FunctionSet header
ctx.redirect(url: string, status?: number)FunctionRedirect client
ctx.sendFile(filePath: string)FunctionSend static file
ctx.ok<T>(data?: T)FunctionShortcut 200 OK (JSON)
ctx.created<T>(data?: T)FunctionShortcut 201 Created
ctx.accepted<T>(data?: T)FunctionShortcut 202 Accepted
ctx.noContent()FunctionShortcut 204 No Content
ctx.badRequest(msg?: string)FunctionShortcut 400 Bad Request
ctx.unauthorized(msg?: string)FunctionShortcut 401 Unauthorized
ctx.forbidden(msg?: string)FunctionShortcut 403 Forbidden
ctx.notFound(msg?: string)FunctionShortcut 404 Not Found
ctx.internalError(msg?: string)FunctionShortcut 500 Internal Server Error

πŸ‘‰ Inspired by Koa, but stricter and typed.

⚠️ TTL and expiration values are always in milliseconds (ms) Example: 60000 = 60 seconds

Routing

Railiz uses a multi-stage routing engine that automatically adapts based on the number of registered routes.

ModeEngineWhen used
linearsimple sequential scan< 100 routes
hybridindexed + LRU cache< 1000 routes
compiledprecompiled 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.

Router Options

const app = new Railiz() // auto mode (recommended)

Or explicitly:

const app = new Railiz({ router: 'linear' })

Available modes:

  • linear β†’ simple matcher (small apps, fast startup)
  • radix β†’ radix tree routing (default fast mode)
  • auto β†’ automatic engine switching based on scale
  • compiled β†’ precompiled route matcher (large-scale production)

Supported Routes

TypePathExample
Static/usersList all users
Param/users/:idGet user by ID

Route Definition

const app = new Railiz()

app.route({ method: 'GET', path: '/users/:id', middleware: [auth], handler: async (ctx) => { return ctx.ok({ id: ctx.params.id }) }, })

Routing Evolution

Railiz automatically upgrades its routing engine as the application scales:

  • < 100 routes β†’ linear scan (minimal overhead, fastest startup)
  • < 1000 routes β†’ hybrid engine (LRU + indexed lookup)
  • 1000 routes β†’ compiled router (precomputed match functions for maximum throughput)

Grouping

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' }))
  })
})

Plugin System

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:

  • Proxy requests: forward requests to another server (reverse proxy)
  • Auth / JWT: add middleware to check tokens

πŸ’‘ Notes:

  • Pipeline order matters: Plugins are executed in the order they are registered. Place app.plugin calls carefully to control execution sequence.
  • Reusability: Plugins can be packaged and reused across multiple apps, making it easy to share common functionality like logging, auth, or proxying.

Middleware

app.use(async (ctx, next) => {
  console.log(ctx.path)
  await next()
})

Error Boundary

app.route({
  method: 'GET',
  path: '/boom',
  handler: async () => {
    throw new Error('boom')
  },
  errorBoundary: async (ctx) => {
    ctx.status = 500
    ctx.body = 'Handled error'
  },
})

Http Error

CaseUse
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',
})

Presets

Built-in Presets:

  • apiPreset – JSON parsing, query parser, optional CORS.
  • loggerPreset – Log request β†’ response time.
  • authPreset – JWT / API key auth middleware.

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)

Hooks

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))

App Factory (createApp)

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.

Background Tasks

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 is a per-request array.
  • Tasks run in parallel, and errors are logged without affecting the response.
  • You can push multiple tasks at once:
ctx.backgroundTasks!.push(task1, task2, task3)

DI (Dependency Injection)

Register DI

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 })
})

Scoped DI behavior

ScopeBehavior
singleton1 instance app
scoped1 instance / request
transientalways create

scoped = per request context

Adapter Support (Quick)

  • Express (expressAdapter)
  • Fastify (fastifyAdapter)
  • Lambda (lambdaAdapter)
  • Cloudflare Workers (workersAdapter)
  • Bun (bunAdapter)
app.use(expressAdapter(app))
export const handler = lambdaAdapter(app)
export default workersAdapter(app)
Bun.serve(bunAdapter(app))
fastify.all('*', fastifyAdapter(app))

Ecosystem (Middleware Runtime)

app.use(errorHandler())
Middleware / FeatureNode.jsLambdaEdgeNotes
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.

Example

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

OpenAPI

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)

Features

  • Automatically collects metadata from your routes.
  • Supports path parameters, query parameters, and request/response bodies.
  • Exposes /openapi.json endpoint for OpenAPI 3 spec.
  • Can mock API responses based on defined schemas.
  • Works with both linear and radix routers.

Use

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)

Accessing OpenAPI

Visit http://localhost:3000/openapi.json to see your API documentation.

Mock Responses

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.

Cache System

Railiz provides a 3-layer caching model:

Cache Architecture

L1 (memo) β†’ L2 (requestCache) β†’ Plugin Cache β†’ Origin (DB/API)

Priority

When cache layers are combined, resolution order is:

  • DI cacheClient (HIGHEST)
  • L1 memo()
  • L2 requestCache()
  • Plugin cache

L1 memo() (In-request)

Use case:

  • deduplicate repeated calls in same request
  • avoid redundant DB/API calls
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:

  • deduplicate repeated calls in same request
  • avoid redundant DB/API calls

L2 requestCache()

Use case:

  • cache API responses
  • reduce DB load
  • shared across requests
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,
  })
})

DI Override

app.inject('cacheClient', new MemoryCache())

Plugin Cache

app.plugin(cachePlugin([new MemoryCache()]))

inject > plugin

Circuit Breaker

Protect your system from cascading failures by short-circuiting unstable services.

Basic usage

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. Use key to isolate circuits per service or dependency.

Each service has its own circuit state

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)
  }
)

Behavior

StateDescription
CLOSEDNormal operation
OPENRequests are blocked (fast fail)
HALF_OPENAllows 1 request to test recovery

Flow

CLOSED --(fail x N)--> OPEN

OPEN --(after resetTimeout)--> HALF_OPEN

HALF_OPEN --(success)--> CLOSED
HALF_OPEN --(fail)--> OPEN

When circuit is OPEN

// HTTP Status: 503
{
  "message": "Service unavailable (circuit open: payment-api)"
}

Use cases

  • External APIs (payment, auth, analytics)
  • Microservices communication
  • Database protection (when unstable)

Tip

// ❌ 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

Architecture

Node HTTP
   ↓
Railiz Core
   ↓
Router (Radix / Linear)
   ↓
Context
   ↓
Handlers

Comparison

railiz is the only one here that gives you deterministic middleware execution.

CriteriaRailizExpress.jsFastifyNestJS
Core conceptExecution engineMinimal frameworkWeb frameworkFull framework
Abstraction levelπŸ”₯ Low (full control)LowMediumHigh
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 caseEngine / custom archSmall apps / legacyAPIs / servicesEnterprise apps

πŸ’‘ Takeaways:

  • Express β†’ Freedom, then chaos.
  • Fastify β†’ Structured, plugin-based.
  • NestJS β†’ Enterprise-ready, heavy, opinionated.
  • Railiz β†’ Lightweight, deterministic, full control β€” build your own backend architecture with guarantees.

Design Principles

  • Deterministic execution > implicit magic
  • Explicit routing > dynamic guessing
  • Runtime control > framework opinion
  • Composition over inheritance

When to Use

  • Build your own backend framework
  • Internal APIs
  • High-performance systems
  • Edge runtimes

Performance

  • Radix routing: O(k)
  • ~2-5x faster than Express (micro benchmarks)

When NOT to use Railiz

  • You want a batteries-included framework β†’ use NestJS
  • You don’t care about execution order β†’ use Express
  • You want conventions over control β†’ use Fastify

πŸ‘‰ Railiz is for engineers who want control.

Philosophy

You control:
- architecture
- data
- plugins

railiz controls:
- execution
- routing
- lifecycle

License

MIT

Keywords

typescript-backend

FAQs

Package last updated on 26 Apr 2026

Did you know?

Socket

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.

Install

Related posts