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

@openapi-spec/router

v0.8.0

Published

Fast HTTP router for OpenAPI path templates, with {name} and {+name} params, built for oRPC

Readme

@openapi-spec/router matches HTTP requests against OpenAPI path templates such as /planets/{id} and /files/{+path}, with no route syntax of its own to translate them into. It is built for how oRPC routes OpenAPI procedures: small, with no dependencies, and faster than rou3 on the route sets oRPC builds.

Usage

import { createPrefixMatcher, Router } from '@openapi-spec/router'

const router = new Router<string>()

router.add('GET', '/planets', 'list')
router.add('GET', '/planets/{id}', 'find')
router.add('GET', '/files/{+path}/raw', 'raw')

router.match('GET', '/planets/earth')
// { data: 'find', params: { id: 'earth' } }

router.match('GET', '/files/docs/a%20b.md/raw')
// { data: 'raw', params: { path: 'docs/a b.md' } }

router.match('GET', '/planets/')
// { data: 'list', params: undefined }

router.match('POST', '/planets')
// undefined

// Whether a request can reach a route under a prefix, such as one of a lazy router
const underUser = createPrefixMatcher('/users/{userId}')

underUser('/users/din/settings') // true
underUser('/projects/42') // false

match takes the request method and its percent-encoded path, without the query string, as new URL(request.url).pathname gives it. Methods match exactly, so pass them in the case the routes use.

Path templates

  • {name} as a whole segment is a param. It matches one non-empty segment. Names are made of letters, digits, _, and -.
  • {+name} as a whole segment is a catch-all param. It matches one or more segments, slashes included, and its value cannot be empty. A path can have one, and segments can follow it, as in /files/{+path}/raw. Adding a path with more than one throws a TypeError.
  • Any other text is literal, including braces that are not a whole segment, as in /a/{id}.json, and characters other routers treat as syntax, such as :, *, ?, (, and ..
  • Literal text matches however the route or the request percent-encodes it: /café matches /caf%C3%A9, and /v1^beta matches /v1%5Ebeta. A %2F stays inside its segment. Text whose percent-encoding is malformed is compared as written.
  • Param values are percent-decoded. A catch-all param joins its decoded segments with /.
  • One trailing slash is ignored, in routes and in requests. Other empty segments are kept: /a//b does not match /a/b.

Priority

When more than one route matches, segments compare from left to right. At each segment a literal beats a param, which beats a catch-all param, and a route that fails further on falls back to the next candidate:

router.add('GET', '/users/me', 'me')
router.add('GET', '/users/{id}', 'user')
router.add('GET', '/users/{id}/posts', 'posts')
router.add('GET', '/{+path}', 'fallback')

router.match('GET', '/users/me') // 'me'
router.match('GET', '/users/m%65') // 'me'
router.match('GET', '/users/me/posts') // 'posts', with { id: 'me' }
router.match('GET', '/users/me/likes') // 'fallback'

Catch-all params that start at the same segment compare the segments after them from the last one, so /files/{+path}/raw beats /files/{+path} for /files/a/raw. A route with the same method and segments as one added earlier, even with other param names, is ignored.

Performance

A request path without % that matches a route without params is found with one lookup. Other paths are split into segments once and walk a tree of literal, param, and catch-all children, so the work grows with the number of segments, not the number of routes. A tree that holds, at many segments in a row, both literals and params a request could take can fall back many times before it finds a match, as with any router that backtracks.

add keeps each route with params as written, and the tree is built as requests reach it: a method's first request that needs the tree adds each route's segments up to its first param, and the segments from each param on wait until a request reaches that point. A process that starts cold and serves a few requests pays only for the methods it serves and the params their requests reach.

Paths with params cost more than static ones, since each of their segments is sliced out and matched on its own. Radix-tree routers such as memoirist and find-my-way compare the path in place and can be faster on them.

Benchmarks compare it with rou3 on route sets like oRPC's. CodSpeed runs them on every pull request to catch regressions.

Sponsors

Like what we build over at middleapi? You can help keep it going through GitHub Sponsors or Open Collective. Every bit helps! 🚀

Organization Sponsors

Sponsors

Backers

With thanks to 40 past sponsors who helped get openapi-spec here.

Open Source Programs

middleapi is supported by these open source programs:

License

Distributed under the MIT License. See LICENSE for more information.