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

dynara

v0.0.5

Published

Simple HTTP framework powered by Bun

Readme

Dynara

NPM version

An extremely simple HTTP framework for Bun — practically a typed wrapper around Bun.serve, with Fastify-style routing and fast schema validation. Made for people switching over from Fastify.

  • Bun only — Node.js and Deno are not supported.
  • Minimal overhead — routing is delegated to Bun's native router.
  • Typed validation — powered by TypeBox, written with the compact compact-json-schema syntax.

Install

bun add dynara

Quick start

import { Router } from 'dynara'

const app = new Router()

app.get('/', () => {
  return { hello: 'world' }
})

app.listen(3000)

A handler may return a plain value (sent as JSON), a Response (sent as-is), or undefined (an empty 200).

Routes & validation

Routes use Bun's native patterns — :param for a single segment, * for a wildcard. The methods are get, post, put, patch, and delete.

Schemas are written with compact-json-schema and validated with TypeBox. Pass them as a route-option object, or as a positional array — [params] / [params, query] for GET, [params, body, query] for the others. Validated req.params, req.query, and req.body are fully typed.

import { schema } from 'compact-json-schema'

const params = schema({ id: 'number' })
const body = schema({ name: 'string', age: 'number?' }) // ? optional, ?? nullable

// Option object
app.post('/users/:id', { params, body }, (req) => {
  req.params.id   // number
  req.body.name   // string
  return { ok: true }
})

// Positional array
app.get('/users/:id', [params], (req) => {
  return { id: req.params.id } // number
})

A few conveniences:

  • Array params are comma-split: GET /items/1,2,3 with { itemIds: { type: 'array', items: 'number' } } yields [1, 2, 3].
  • Query booleans accept ?flag=true or a bare ?flag.
  • Dates: a "date" field accepts an ISO-8601 string ("2020-01-02", optionally with a time part) or epoch milliseconds, and is decoded into a JS Date before your handler runs — req.body.startsAt instanceof Date. Works in bodies, query strings, and params; "date?" / "date??" behave like every other type.
  • req.raw exposes the underlying BunRequest, and req.server the Bun Server.
const event = schema({ title: 'string', startsAt: 'date', endsAt: 'date??' })
app.post('/events', { body: event }, (req) => {
  return { day: req.body.startsAt.getDay() } // startsAt is a Date
})

Hooks

app.addHook('onRequest', (req) => {
  // runs before every handler; throw to short-circuit the request
})

app.addHook('onListen', (server) => {
  console.log(`Listening on ${server.url}`)
})

Plugins & context

register mounts a group of routes under a prefix. Type the app with a context type to share data attached by hooks:

type Ctx = { user: { id: number } }

app.register((users: Router<Ctx>) => {
  users.addHook('onRequest', (req) => { req.user = { id: 1 } })
  users.get('/me', (req) => req.user)
}, { prefix: '/users' })

For composable, reusable plugins there is the dynara() builder. use adds plugins, routes adds handlers, and the result can be passed to register:

import { dynara, Router } from 'dynara'

const useAuth = (app: Router<Ctx>) => {
  app.addHook('onRequest', (req) => { req.user = { id: 1 } })
}

const users = dynara<Ctx>()
  .use(useAuth)
  .routes((app) => {
    app.get('/me', (req) => req.user)
  })

app.register(users, { prefix: '/users' })

Errors

Throw HTTPError to send an explicit status code; validation failures are turned into 400 responses automatically.

import { HTTPError } from 'dynara'

app.get('/secret', () => {
  throw new HTTPError('Forbidden', 403)        // text body
  // throw new HTTPError({ reason: 'forbidden' }, 403)  // JSON body
})

Testing

inject() dispatches a request through your routes in-process — no server, no socket — and returns a real Response. It reuses the same routing, validation, and error handling as a live server.

import { test, expect } from 'bun:test'

test('returns a user', async () => {
  const res = await app.inject('/users/1')
  expect(res.status).toBe(200)
  expect(await res.json()).toEqual({ id: 1 })
})

// With a body:
await app.inject({ method: 'POST', url: '/users', body: { name: 'Alice' } })

Under inject() there is no Bun Server, so req.server is undefined and WebSocket upgrades are not exercised.

WebSockets

app.registerWsHandler('/ws', {
  open(ws) { ws.send('hello') },
  message(ws, msg) { ws.send(msg) },
})

License

MIT