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

@cuboapp/http-server

v1.0.12

Published

Minimal, type-safe HTTP server with routing, params, body parsing and auth for Node.js

Readme

@cuboapp/http-server

A minimal, type-safe HTTP server for Node.js built on the native node:http module. It adds pattern-based routing with named (and optional) parameters, automatic body parsing, query parsing, CORS, and a pluggable authorization hook — with full TypeScript inference for request/response shapes.

  • Zero runtime dependencies — only Node's built-ins.
  • ESM, ships type declarations.
  • Type-safe routes — describe Body/Params/Query per route and get inference in the handler.

Install

npm install @cuboapp/http-server

Requires Node.js >= 18.

Quick start

import { createHttpServer } from '@cuboapp/http-server'

const server = createHttpServer({ host: '0.0.0.0', port: 3000, debug: true })

await server.registerRoute('GET', '/health', async () => ({ ok: true }))

await server.registerRoute('GET', '/users/:id', async ({ params }) => {
  return { id: params.id }
})

await server.registerRoute('POST', '/users', async ({ body }) => {
  return { code: 201, created: body }
})

await server.start()
// Http server started: http://0.0.0.0:3000

Routing

Register routes with registerRoute(method, path, handler, opts?). Supported methods: GET, POST, PUT, PATCH, DELETE, OPTIONS.

Parameters

Use :name for a required parameter and :name? for an optional one:

await server.registerRoute('GET', '/posts/:id', async ({ params }) => params.id)
await server.registerRoute('GET', '/posts/:id?', async ({ params }) => params.id ?? 'all')

A missing required parameter responds with 400.

Query

Query string values are parsed into ctx.query:

// GET /search?q=hello
await server.registerRoute('GET', '/search', async ({ query }) => ({ q: query.q }))

Typed routes

Pass a DTO describing the route to get full inference for body, params, and query:

type CreateUser = {
  Body: { name: string; email: string }
  Params: { id: string }
  Query: { invite?: string }
}

await server.registerRoute<CreateUser>('POST', '/users/:id', async ({ body, params, query }) => {
  // body.name, params.id, query.invite are all typed
  return { code: 201, id: params.id, name: body.name }
})

Request body

For POST, PUT, and PATCH the body is read and parsed automatically into ctx.body:

  • application/x-www-form-urlencoded → parsed object
  • anything else → parsed as JSON, falling back to the raw string if parsing fails

Reading the body times out after 2 seconds and responds with 408. To read the stream yourself (e.g. for uploads or streaming), set manualBody and use ctx.request directly:

await server.registerRoute('POST', '/upload', async ({ request }) => {
  // consume `request` as a raw stream yourself
  return { ok: true }
}, { manualBody: true })

Responses

The value a handler returns determines the HTTP response:

| Return value | Status | Body | | --- | --- | --- | | string / number / boolean | 200 | the value as text | | null / undefined | 200 | empty | | array | 200 | JSON | | object | code ?? 200 | JSON of the remaining fields |

For an object response, the reserved keys code, message, and headers are interpreted and removed from the JSON body; everything else is serialized:

async () => ({
  code: 201,                          // -> HTTP status
  headers: { contentType: 'application/json; charset=utf-8' },
  id: 1,                              // -> body: { "id": 1, "name": "Ada" }
  name: 'Ada'
})

Writing the response manually

To take full control of the response (custom streaming, redirects, etc.), write to ctx.response and return { raw: true }. The server will not touch the response afterwards:

await server.registerRoute('GET', '/stream', async ({ response }) => {
  response.writeHead(200, { 'Content-Type': 'text/plain' })
  response.end('streamed')
  return { raw: true }
})

Errors

Throw an object with code and message to send an error response:

await server.registerRoute('GET', '/secret', async () => {
  throw { code: 403, message: 'Forbidden' }
})

Unmatched routes respond with 404. CORS preflight (OPTIONS) requests are answered automatically with 204 and permissive CORS headers.

Authorization

Provide an auth handler and opt routes in (or default all routes in). The handler receives the server and request, and can attach data to request.auth or throw to reject:

type Ctx = { auth: { userId: string } }

const server = createHttpServer<Ctx>({
  host: '0.0.0.0',
  port: 3000,
  auth: {
    default: false, // set true to require auth on every route unless overridden
    handler: async ({ request }) => {
      const token = request.headers['authorization']
      if (!token) throw { code: 401, message: 'Unauthorized' }
      request.auth = { userId: 'resolved-from-token' }
    }
  }
})

// opt a single route into auth
await server.registerRoute('GET', '/me', async ({ auth }) => ({ userId: auth.userId }), { authorize: true })

API

createHttpServer<C>(options)

Creates a server instance.

| Option | Type | Description | | --- | --- | --- | | host | string | Host to bind. | | port | number | Port to listen on. | | debug | boolean | Log startup and request errors. | | auth.default | boolean | Require auth on all routes by default. | | auth.handler | (ctx) => void \| Promise<void> | Authorization hook. |

HttpServer methods

  • registerRoute(method, path, handler, opts?) — register a route. opts accepts { authorize?, manualBody? }.
  • start() — initialize and begin listening.
  • stop() — close the server.
  • init() — build the underlying http.Server without listening.
  • getInstance() — the underlying node:http Server (available after init()/start()).

httpRoute(method, path, handler, opts?)

A small helper to declare a route descriptor separately from a server instance, useful for collecting routes across modules:

import { httpRoute } from '@cuboapp/http-server'

export const getHealth = httpRoute('GET', '/health', async () => ({ ok: true }))

// later, against a server:
await server.registerRoute(getHealth.method, getHealth.path, getHealth.handler, getHealth.opts)

License

MIT © CuboSoft