npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

railiz

v0.5.0

Published

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

Readme

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


Routing

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.

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

| Type | Path | Example | |--------|--------------|----------------------------------| | Static | /users | List all users | | Param | /users/:id | Get 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

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

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

| Scope | Behavior | | --------- | -------------------- | | singleton | 1 instance app | | scoped | 1 instance / request | | transient | always 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 / 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.


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

Per-service isolation (recommended)

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

| State | Description | | --------- | --------------------------------- | | CLOSED | Normal operation | | OPEN | Requests are blocked (fast fail) | | HALF_OPEN | Allows 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.

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

  • 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