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

v2.0.0

Published

Lightweight HTTP engine built on Railiz for building modular, type-safe, and deterministic backend APIs with plugin-based architecture.

Readme

🌐 railiz-http

NPM Downloads Bundle Size

LIVE EXAMPLE

🚀 A smart HTTP client layer built for Railiz ecosystem

Built for control, caching, retries, and resilience
Designed for distributed backend systems


Why railiz-http?

  • ⚡ Global + per-request config merging
  • 🧠 L1 / L2 caching (memo + requestCache)
  • 🔁 Retry with exponential backoff
  • 🛡 Circuit breaker support
  • ⏱ Timeout control
  • 🔌 Middleware pipeline (like Koa, but typed)
  • 🌍 BaseURL support
  • 🧩 Fully compatible with Railiz Context

Installation

npm install railiz-http railiz-resilience railiz

Quick example

import { useHttp } from 'railiz-http'

app.get('/users/:id', async (ctx) => {
  const http = useHttp({
    context: ctx,
    options: {
      baseURL: 'https://jsonplaceholder.typicode.com',
    },
  })

  const user = await http.get(`/users/${ctx.params.id}`)

  return ctx.json({ user })
})

Global Config

You can define global HTTP behavior once:

import { createHttpContext, setGlobalHttpConfig, useHttp } from 'railiz-http'

setGlobalHttpConfig({
  baseURL: 'https://jsonplaceholder.typicode.com',

  timeout: 2000,
  retry: {
    attempts: 3,
    delay: 300,
  },

  cache: {
    ttl: 5000,
    swr: 10000,
  },
})

Context Model

Standalone Context

const ctx = createHttpContext()
// const ctx = createHttpContext(your-context)

const http = useHttp({ context: ctx, options: { timeout: 3000 } })

Railiz Context

app.get('/memo-demo', async (ctx) => {
  const http = useHttp({ context: ctx })
})

Global config is read-only and only used as a base for per-request configuration.


Caching System

L1 + L2 Cache

| Layer | Type | Scope | |-------|-------------|--------------| | L1 | memo | per request | | L2 | requestCache| shared cache |


Memo (L1 Cache - Per Request)

app.get('/memo-demo', async (ctx) => {
  const http = useHttp({
    context: ctx,
    options: {
      baseURL: 'https://jsonplaceholder.typicode.com',
    },
  })

  const user1 = await http.get('/users/1', {
    memo: true,
  })

  const user2 = await http.get('/users/1', {
    memo: true,
  })

  return ctx.json({
    memoHit: user1 === user2, // true (same reference)
    user1,
    // user2,  // "user2": "[Circular]",
    user2Copy: JSON.parse(JSON.stringify(user2)) // same json
  })
})

Memo with TTL

const user = await http.get('/users/1', {
  memo: {
    ttl: 5000,
    debug: true,
  },
})

L2 Cache (shared cache)

const user = await http.get('/users/1', {
  cache: {
    ttl: 10000,
    swr: 15000,
    key: 'user:1',
    tags: ['users'],
  },
})

Memo + Cache hybrid

app.get('/users/:id', async (ctx) => {
  const http = useHttp({
    context: ctx,
    options: {
      baseURL: 'https://jsonplaceholder.typicode.com',
    },
  })

  const user = await http.get(`/users/${ctx.params.id}`, {
    // L1: memo (per-request dedupe)
    memo: {
      ttl: 5000,
      debug: true,
    },

    // L2: shared cache (cross-request)
    cache: {
      ttl: 10000,
      swr: 20000,
      key: `user:${ctx.params.id}`,
      tags: ['users'],
    },
  })

  return ctx.json({ user })
})

Compare: Memo + Cache

| Feature | Memo (L1) | Cache (L2) | |-----------|---------------|--------------| | Scope | per request | shared | | Lifetime | ctx lifecycle | TTL based | | Purpose | dedupe calls | persist data |

Memo is NOT persistence. It only deduplicates in the same request lifecycle.

Key Insight

  • memo → prevents duplicate calls within the same request
  • cache → persists data across requests
  • Using both together → optimal performance + consistency

Retry

await http.get('/unstable-api', {
  retry: {
    attempts: 3,
    delay: 200,
  },
})

Behavior

attempt 1 → fail
attempt 2 → retry (delay 200ms)
attempt 3 → retry (400ms)

Circuit Breaker

Protect external APIs from cascading failure.

Basic usage

const data = await http.get('/payments', {
  breaker: {
    key: 'payment-service',
  },
})

Full production config

app.get(
  '/checkout',
  safeHandler(async (ctx) => {
    const http = useHttp({
      context: ctx,
      options: {
        baseURL: 'https://api.payment.com',
        timeout: 3000,
      },
    })

    const payment = await http.post(
      '/charge',
      {
        amount: 100,
        currency: 'USD',
      },
      {
        // ❌ NEVER cache payment
        cache: false,
        memo: false,

        // 🔁 retry (network / transient error)
        retry: {
          attempts: 2,
          delay: 200,
        },

        // 🛡 circuit breaker: protect downstream
        breaker: {
          key: 'payment-service',
          failureThreshold: 3,
          resetTimeout: 15000,
        },
      },
    )

    return {
      success: true,
      payment,
    }
  }),
)

Real production pattern (microservices)

const user = await http.get('/users/1', {
  // 🛡 protect downstream service
  breaker: {
    key: 'user-service',
    failureThreshold: 5,
    resetTimeout: 10000,
  },

  // 🔁 retry: transient error
  retry: {
    attempts: 2,
    delay: 200,
  },

  // 🧠 L1 memo (dedupe request)
  memo: true,

  // 💾 L2 cache (cross-request)
  cache: {
    key: 'user:1', // ✅ explicit
    ttl: 10000,
    swr: 20000,
    tags: ['users'], // ✅ for invalidate
  },
})

Note: advanced breaker options depend on railiz-resilience configuration.


Timeout

await http.get('/slow-api', {
  timeout: 1500,
})

If request exceeds timeout → aborted automatically.


Middleware

http.use(async (ctx, next) => {
  console.log('Request:', ctx.request?.url)
  return next()
})

Unlike Railiz middleware (server-level), this runs at the HTTP client layer — intercepting outbound requests (fetch calls).


Railiz App

import {
  Railiz,
  bodyParser,
  cors,
  logger,
  json,
  normalizeHeaders,
  rateLimit,
  safeHandler,
} from 'railiz'

import { cacheControl } from 'railiz-resilience'
import { useHttp, setGlobalHttpConfig } from 'railiz-http'

// Global config
setGlobalHttpConfig({
  baseURL: 'https://jsonplaceholder.typicode.com',
  cache: { ttl: 5000 },
  timeout: 2000,
  retry: 2,
})

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

app.use(normalizeHeaders())
app.use(bodyParser({ json: { limit: '2mb' } }))
app.use(logger())
app.use(cors({ origin: '*' }))
app.use(json())
app.use(rateLimit({ limit: 100, windowMs: 60000 }))

// Cache helper
function setCache(ctx: any, options: any) {
  cacheControl({
    set: (key, value) => ctx.set(key, value),
  })(options)
}

// Home route (cache + swr)
app.get(
  '/',
  safeHandler(async (ctx) => {
    const http = useHttp({ context: ctx })

    const user = await http.get('/users/1', {
      cache: {
        ttl: 5000,
        swr: 15000,
        tags: ['users'],
      },
    })

    const posts = await http.get('/posts', {
      params: { _limit: 5 },
      cache: {
        ttl: 3000,
        swr: 10000,
        tags: ['posts'],
      },
    })

    setCache(ctx, {
      public: true,
      maxAge: 5000,
      swr: 10000,
    })

    return { user, posts }
  }),
)

// User route
app.get(
  '/users/:id',
  safeHandler(async (ctx) => {
    const http = useHttp({ context: ctx })

    const user = await http.get(`/users/${ctx.params.id}`, {
      cache: {
        ttl: 10000,
        key: `user:${ctx.params.id}`,
        tags: ['users'],
      },
    })

    setCache(ctx, {
      public: true,
      maxAge: 10000,
    })

    return { user }
  }),
)

// Posts route
app.get(
  '/posts',
  safeHandler(async (ctx) => {
    const http = useHttp({ context: ctx })

    const posts = await http.get('/posts?_limit=10', {
      cache: {
        ttl: 5000,
        swr: 20000,
        tags: ['posts'],
      },
    })

    setCache(ctx, {
      public: true,
      maxAge: 5000,
      swr: 20000,
    })

    return { posts }
  }),
)

// Checkout (breaker + retry)
app.get(
  '/checkout',
  safeHandler(async (ctx) => {
    const http = useHttp({
      context: ctx,
      config: {
        baseURL: 'https://api.payment.com',
      },
    })

    const payment = await http.post(
      '/charge',
      {
        amount: 100,
        currency: 'USD',
      },
      {
        breaker: {
          key: 'payment-service',
          failureThreshold: 3,
          resetTimeout: 15000,
        },
        retry: {
          attempts: 2,
          delay: 200,
        },
        cache: false,
      },
    )

    return { payment }
  }),
)

Standalone Usage (SDK mode)

import { createHttpContext, useHttp } from 'railiz-http'

const ctx = createHttpContext()

const http = useHttp({
  context: ctx,
  options: {
    baseURL: 'https://jsonplaceholder.typicode.com',
  },
})

async function main() {
  const user = await http.get('/users/1', {
    cache: { ttl: 5000 },
  })

  const posts = await http.get('/posts?_limit=3', {
    cache: true,
  })

  console.log(user, posts)
}

main()

Architecture

Railiz Context
   ↓
useHttp (merge config)
   ↓
HttpClient
   ↓
Middleware pipeline
   ↓
Memo (L1)
   ↓
Cache (L2)
   ↓
Retry / Timeout / Circuit Breaker
   ↓
fetch()

When to use

  • Microservices communication
  • Backend aggregation layer (BFF)
  • Payment / auth / external APIs
  • Distributed system orchestration

When NOT to use

  • Frontend data fetching (use React Query / SWR instead)
  • Simple one-off scripts
  • Static data requests without retry/caching needs

What makes it different?

Unlike traditional HTTP clients (Axios, fetch wrappers), railiz-http:

  • Runs inside request context
  • Supports multi-layer caching (L1 + L2)
  • Designed for backend orchestration (not UI calls)
  • Integrates with DI + middleware pipeline
  • Enables deterministic request behavior

Philosophy

HTTP should be predictable, cached, and resilient by default.


License

MIT