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

@sigitex/route

v1.0.3

Published

A server-side web framework.

Downloads

67

Readme

@sigitex/route

A server-side web framework.

bun add @sigitex/route

Note: This package currently exports TypeScript sources directly. A TypeScript-compatible runtime or bundler (Bun, etc.) is required.

Quick Start

import { route, get, post, prefix, cors, hardened, cookies } from "@sigitex/route"
import { bun } from "@sigitex/route/bun"

const fetch = route(
  bun({ assets: "./public" }),
  get("/health", () => ({ status: "ok" })),
  prefix("/api/", [cors(), cookies(), hardened()],
    get("/users", async () => {
      return Response.json(await getUsers())
    }),
    get("/users/:id", async ({ params }: { params: { id: string } }) => {
      return Response.json(await getUser(params.id))
    }),
    post("/users", async ({ request }: { request: Request }) => {
      const body = await request.json()
      return Response.json(await createUser(body))
    }),
  ),
)

Bun.serve({ fetch })

On Cloudflare Workers:

import { route, get } from "@sigitex/route"
import { cloudflare } from "@sigitex/route/cloudflare"

export default {
  fetch: route(
    cloudflare(),
    get("/hello", () => ({ hello: "world" })),
  ),
}

route(...handlers)

Creates a fetch function (request: Request, env: Env) => Promise<Response> from a list of handlers. Handlers are tried in order; the first to return a value produces the response. If none match, a 404 is returned.

const fetch = route(handler1, handler2, handler3)

An optional RouterOptions object can be passed as the first argument:

const fetch = route({ container, middlewares: [cors()] }, handler1, handler2)

Falsy values (null, undefined, false, 0) are silently ignored, allowing conditional handlers:

route(
  isDev && get("/debug", debugHandler),
  get("/", homeHandler),
)

RouterOptions

| Option | Type | Description | | ------------- | ----------------- | ------------------------------------------------------ | | container | Container | A @sigitex/bind IoC container for dependency injection | | middlewares | RouteMiddleware[] | Global middlewares applied to every dispatched handler |

Handlers

Handlers are functions that receive a context object and return a Response, a JSON-serializable value, or undefined to skip.

get(path, handler, ...middlewares)

Matches GET requests against path. Path parameters use regexparam syntax (:param, *).

get("/users/:id", ({ params }: { params: { id: string } }) => {
  return Response.json({ id: params.id })
})

post(path, handler, ...middlewares)

Matches POST requests.

put(path, handler, ...middlewares)

Matches PUT requests.

del(path, handler, ...middlewares)

Matches DELETE requests.

patch(path, handler, ...middlewares)

Matches PATCH requests.

pattern(method, path, handler, ...middlewares)

Generic version -- pass null as the method to match any HTTP method.

pattern(null, "/any-method/:id", handler)
pattern("GET", "/explicit", handler)

prefix(prefix, ...handlers)

Groups handlers under a URL prefix. The prefix is stripped from the URL before child handlers see it.

prefix("/api/v1/",
  get("/users", listUsers),   // matches /api/v1/users
  get("/posts", listPosts),   // matches /api/v1/posts
)

Accepts an optional middlewares array as the second argument:

prefix("/api/", [cors(), bodyLimit()],
  post("/upload", uploadHandler),
)

mount(fetchFn)

Wraps a standard (request: Request) => Promise<Response> function as a handler. Useful for mounting sub-applications or external fetch handlers.

mount(subApp.fetch)

assets()

Serves static files via the platform's Assets binding. Returns undefined on 404 so subsequent handlers can match.

route(bun(), assets(), get("/", homeHandler))

app(routes)

Serves index.html for paths matching a client-side RouteTree. Intended for single-page applications where the client handles routing.

app({
  users: "/users",
  user: "/users/:id",
  settings: { general: "/settings/general" },
})

noop

A handler that does nothing and returns undefined. Used internally by middleware composition.

use(middlewares, ...handlers)

Applies a set of middlewares to a group of handlers without creating a prefix.

use([cors(), cookies()],
  get("/a", handlerA),
  get("/b", handlerB),
)

filter(predicate)

Higher-order function that conditionally runs a handler based on a predicate.

const onlyJson = filter(({ request }) =>
  request.headers.get("Accept")?.includes("application/json") ?? false
)

onlyJson(get("/data", dataHandler))

https()

Redirects HTTP requests to HTTPS with a 301.

route(https(), get("/", homeHandler))

www(options)

Redirects non-www requests to the www subdomain.

www({ secure: true })  // also upgrades to https

Middleware

Middlewares are objects with optional before and after hooks. before runs before the handler; after runs after. Either can return a Response to short-circuit.

const myMiddleware: RouteMiddleware = {
  before: ({ request, bind }) => {
    bind({ startTime: Date.now() })
  },
  after: ({ response, startTime }) => {
    response.headers.set("X-Duration", String(Date.now() - startTime))
  },
}

cors(options?)

Handles CORS preflight and response headers.

| Option | Type | Default | | --------------- | ------------------------------------------------- | ---------- | | origin | string \| string[] \| (origin: string) => boolean | "*" | | methods | string[] | all standard | | allowHeaders | string[] | mirrors request | | exposeHeaders | string[] | -- | | credentials | boolean | false | | maxAge | number | -- |

cookies()

Parses request cookies and collects Set-Cookie headers on the response. Binds a Cookies object to context:

cookies.get("session")           // read
cookies.set("session", token, {  // write
  httpOnly: true,
  secure: true,
  sameSite: "strict",
  maxAge: 86400,
  path: "/",
})

CookieOptions

domain, expires, httpOnly, maxAge, path, sameSite ("strict" | "lax" | "none"), secure.

bodyLimit(options?)

Rejects requests exceeding a body size or with disallowed content types.

| Option | Type | Default | | -------------- | ---------- | --------- | | maxSize | number | 1 MB | | contentTypes | string[] | any |

cache(options)

Sets Cache-Control (and optionally Vary) headers on responses.

cache({ public: true, maxAge: 3600 })
cache("no-store")

CacheOptions

public, private, maxAge, sMaxAge, noCache, noStore, mustRevalidate, proxyRevalidate, immutable, staleWhileRevalidate, staleIfError, vary.

csp(options)

Sets Content-Security-Policy headers. Supports automatic nonce generation via the CSP.nonce symbol -- the nonce is bound to context as cspNonce.

import { CSP } from "@sigitex/route"

csp({
  defaultSrc: [CSP.self],
  scriptSrc: [CSP.self, CSP.nonce],
  styleSrc: [CSP.self, CSP.unsafeInline],
  imgSrc: [CSP.self, CSP.data],
  reportOnly: true,
})

csrf(options?)

Double-submit cookie CSRF protection. Requires cookies() in the middleware stack.

| Option | Type | Default | | --------- | ---------- | -------------------------------- | | cookie | string | "csrf-token" | | header | string | "X-CSRF-Token" | | methods | string[] | POST, PUT, PATCH, DELETE |

rateLimit(options?)

IP-based rate limiting with pluggable storage.

| Option | Type | Default | | --------- | ------------------ | ------------------------ | | window | number (seconds) | 60 | | max | number | 100 | | key | (ctx) => string | rateLimit.ip | | store | RateLimitStore | rateLimit.memory() | | headers | boolean | true |

Built-in helpers:

  • rateLimit.ip -- key extractor using CF-Connecting-IP or X-Forwarded-For
  • rateLimit.memory() -- in-memory sliding window store

hardened(options?)

Convenience bundle applying noSniff, frameGuard, referrerPolicy, and hsts. Any can be disabled:

hardened()                              // all defaults
hardened({ hsts: false })               // skip HSTS
hardened({ frameGuard: "sameOrigin" })  // override frame guard

hsts(options?)

Sets Strict-Transport-Security. Defaults: max-age=31536000; includeSubDomains.

| Option | Type | Default | | ------------------- | --------- | ----------- | | maxAge | number | 31536000 | | includeSubDomains | boolean | true | | preload | boolean | false |

noSniff

Sets X-Content-Type-Options: nosniff.

frameGuard.deny / frameGuard.sameOrigin

Sets X-Frame-Options.

referrerPolicy.*

Pre-built policies: noReferrer, noReferrerWhenDowngrade, origin, originWhenCrossOrigin, sameOrigin, strictOrigin, strictOriginWhenCrossOrigin, unsafeUrl.

requestId(options?)

Reads or generates a request ID, binds it to context as requestId, and echoes it on the response.

| Option | Type | Default | | ---------- | -------------- | -------------------- | | header | string | "X-Request-Id" | | generate | () => string | crypto.randomUUID |

setHeader(header, value)

Sets a header on every response. Returns a ResponseHandler (use as an after hook).

{ after: setHeader("X-Powered-By", "sigitex") }

Errors

Error classes that can be thrown from handlers:

| Class | Status | Default Message | | ------------------ | ------ | ------------------------ | | RouterError | any | -- | | NotFound | 404 | "Not found." | | MethodNotAllowed | 405 | "Method not allowed." | | InvalidRequest | 400 | "Invalid request." | | ServerError | -- | "Internal error." |

Internals

Request Lifecycle

When route() is called it creates a Router and returns its route method. On each request:

  1. A RequestContext is created with request, env, url, bind, and dispatch.
  2. If a Container was provided, it is cloned and the context is bound into it.
  3. Each handler is dispatched in order. The first to return a non-undefined value wins.
  4. If no handler matches, a JSON 404 is returned. Uncaught errors produce a JSON 500.

bind(bindings)

Merges key-value pairs into the current request's context. With a container, values are registered in the IoC container; without one, they're Object.assigned directly onto the context object. This is how middleware like cookies() and requestId() expose their state to downstream handlers.

dispatch(handler, middlewares)

Runs a single handler through a middleware chain:

  1. Each middleware's before hook runs in order. If any returns a value, it short-circuits as the response.
  2. The handler is invoked. If it returns undefined, dispatch returns undefined (no match).
  3. The response is bound to context via bind({ response }).
  4. Each middleware's after hook runs in order. If any returns a value, it replaces the response.
  5. The final response is returned.

Both bind and dispatch are exposed on the context, so handlers like prefix and pattern can create nested dispatch chains with their own middleware stacks.

Container Integration

When RouterOptions.container is provided (a @sigitex/bind Container), the router uses dependency injection instead of plain context objects:

  • The container is cloned per-request so concurrent requests don't share state.
  • bind() registers values in the container rather than mutating the context.
  • invoke() calls container.call(handler) instead of handler(context), letting the container resolve handler parameters by type.

Without a container, handlers receive the context object directly as their first argument.

Response Coercion

Return values from handlers are coerced: Response instances pass through as-is; anything else is JSON.stringifyed into a Response. Returning undefined signals "no match" and the next handler is tried.