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

@sirmekus/uzor

v1.0.0

Published

Lightweight, type-safe route generator. Define routes in a config file and resolve them anywhere - with param interpolation, query-string support, and optional absolute URLs.

Downloads

103

Readme

@sirmekus/uzor

Lightweight, type-safe route generator for TypeScript and JavaScript projects.

Define your routes once in a config file. Resolve them anywhere — with {param} interpolation, automatic query-string handling, and optional absolute URLs.

Igbo word of the day: Uzor ,in Igbo language, means "road" 😉


Why uzor?

In most frontend projects, URLs are written as raw strings scattered across components, hooks, and services:

fetch(`/api/users/${userId}/posts?page=${page}`)
axios.get(`/api/posts/${postId}/comments/${commentId}`)
router.push(`/dashboard/${orgId}/settings`)

This pattern silently accumulates a class of bugs that are easy to introduce and hard to track down:

  • Typos are invisible. /api/usres/42 compiles and ships without complaint.
  • Refactoring is risky. Renaming or restructuring an endpoint means hunting through the entire codebase with find-and-replace, hoping nothing is missed.
  • Inconsistency creeps in. One call encodes params, another doesn't. One appends a trailing slash, another doesn't. Edge cases in query-string serialisation (null, undefined, special characters) are handled differently in every file.
  • There is no single source of truth. Routes live wherever they happen to be used, making it impossible to audit what endpoints a project actually calls.

uzor solves all of this by moving routes to a single config file and providing one function — route() — to resolve them. The rest of the codebase never touches a URL string directly.

What you gain

A single source of truth. Every route the project uses is declared in one place. Adding, removing, or renaming an endpoint is a one-line change, and every call site updates automatically.

Compile-time safety. Route names are typed as keyof of your config. Passing a name that doesn't exist is a TypeScript error before the code ever runs. No more silent 404s caused by a mistyped route name.

Consistent URL construction — always. Placeholders are URI-encoded. Unused params become a properly formatted query string. Trailing slashes are normalised. null and undefined values are dropped cleanly. You write none of this logic yourself, and it behaves the same way everywhere.

Autocomplete on route names. Because defineRoutes() preserves the literal type of your config, editors show a dropdown of valid route names as you type the first argument to route(). Discovering available routes becomes as easy as pressing Ctrl+Space.

Framework and environment agnostic. uzor has zero dependencies and no opinions about your stack. It works in the browser, in Node.js, in React, Vue, Svelte, or plain TypeScript. The router is a plain object, not a hook or a context — create it once, import it anywhere.


Installation

npm install @sirmekus/uzor

No peer dependencies. Works in any environment: browser, Node.js, React, Vue, plain TS.


Quick start

1. Define your routes

Create a config file — the conventional name is routes.config.ts, but any name works.

// routes.config.ts
import { defineRoutes } from '@sirmekus/uzor';

export default defineRoutes({
  'home':                  '/',
  'dashboard':             '/dashboard',
  'api.users':             '/api/users/{id}',
  'api.posts':             '/api/posts/{postId}/comments/{commentId}',
  'api.search':            '/api/search',
  'auth.login':            'https://auth.example.com/v1/login',
});

2. Create a router and export route

Call createRouter once in a dedicated file and export the route function. Import it anywhere you need to resolve a URL.

// router.ts
import { createRouter } from '@sirmekus/uzor';
import routes from './routes.config';

export const { route } = createRouter(routes);

3. Import and use route wherever you need it

// any file in your project
import { route } from './router';

route('home')                                           // '/'
route('api.users', { id: 42 })                         // '/api/users/42'
route('api.posts', { postId: 5, commentId: 3 })        // '/api/posts/5/comments/3'
route('api.search', { q: 'hello world', page: 2 })     // '/api/search?q=hello%20world&page=2'
route('home', {}, true)                                 // 'https://example.com'

API

defineRoutes(config)

An identity helper used when declaring your route config. It returns the config object unchanged but preserves the full TypeScript literal type, which enables autocomplete on route names throughout your app.

import { defineRoutes } from '@sirmekus/uzor';

export default defineRoutes({
  'api.users': '/api/users/{id}',
  'api.posts': '/api/posts/{postId}',
});

Without defineRoutes, TypeScript infers Record<string, string> and you lose name autocomplete.


createRouter(routes)

Takes your route config and returns a { route, routes } object.

import { createRouter } from '@sirmekus/uzor';
import routes from './routes.config';

const { route, routes: allRoutes } = createRouter(routes);

| Property | Type | Description | |----------|------|-------------| | route | RouteFn<T> | Resolves a named route to a URL string. | | routes | T | The original config passed in. |


route(name, params?, absolute?)

Resolves a named route to a URL string.

| Argument | Type | Default | Description | |------------|----------------|---------|-------------| | name | keyof T | — | A key from your route config. TypeScript will error on unknown names. | | params | RouteParams | {} | Values to interpolate into {placeholders} and/or append as query-string entries. | | absolute | boolean | false | When true, prepends window.location.protocol + '//' + window.location.host. Browser-only — throws in Node.js/SSR. |

Throws Error if name is not in the config.


How URL resolution works

Given the template /api/posts/{postId}/comments/{commentId} and params:

Step 1 — Interpolate placeholders
  {postId}    → encodeURIComponent(params.postId)
  {commentId} → encodeURIComponent(params.commentId)

Step 2 — Remove unmatched placeholders
  Any {placeholder} with no matching param key is stripped.

Step 3 — Strip trailing slash
  '/users/' → '/users'   (bare '/' is kept as-is)

Step 4 — Append remaining params as query string
  Keys not consumed by a placeholder → '?key=value&key2=value2'
  If the template already contains '?', uses '&' instead.

Step 5 — Make absolute (optional)
  Prepends window.location.protocol + '//' + window.location.host

undefined, null, and '' param values are silently ignored — they neither fill a placeholder nor appear in the query string.


Examples

Path interpolation

route('api.users', { id: 99 })
// '/api/users/99'

Query string (unused params)

route('api.search', { q: 'cats', page: 2, limit: 10 })
// '/api/search?q=cats&page=2&limit=10'

Mixed — some params fill placeholders, rest become query string

// template: '/api/posts/{postId}'
route('api.posts', { postId: 5, sort: 'desc', page: 1 })
// '/api/posts/5?sort=desc&page=1'

Optional params — omit by passing undefined or null

// template: '/api/users/{id}/avatar'
route('api.users.avatar', { id: undefined })
// '/api/users/avatar'  — {id} is stripped, trailing slash removed

Absolute URL (browser only)

// window.location = 'https://app.example.com/...'
route('dashboard', {}, true)
// 'https://app.example.com/dashboard'

External / absolute template

Templates can be full URLs too:

defineRoutes({
  'auth.login': 'https://auth.example.com/v1/login',
})

route('auth.login', { redirect: '/dashboard' })
// 'https://auth.example.com/v1/login?redirect=%2Fdashboard'

TypeScript

Route names are fully type-checked. Passing an unknown name is a compile-time error:

const { route } = createRouter(defineRoutes({
  'home':      '/',
  'api.users': '/api/users/{id}',
}));

route('home')           // OK
route('api.users', { id: 1 }) // OK
route('bad.name')       // TS Error: Argument of type '"bad.name"' is not assignable

The RouteParams type accepts string | number | boolean | null | undefined values, so you can pass numbers directly without converting:

route('api.users', { id: 42, page: 3, active: true })

Organising large configs

Split routes by domain and merge them before passing to createRouter:

// routes/api.ts
import { defineRoutes } from '@sirmekus/uzor';
export const apiRoutes = defineRoutes({
  'api.users': '/api/users/{id}',
  'api.posts': '/api/posts/{postId}',
});

// routes/auth.ts
import { defineRoutes } from '@sirmekus/uzor';
export const authRoutes = defineRoutes({
  'auth.login':  'https://auth.example.com/v1/login',
  'auth.logout': 'https://auth.example.com/logout',
});

// routes/index.ts
import { createRouter } from '@sirmekus/uzor';
import { apiRoutes }  from './api';
import { authRoutes } from './auth';

export const { route } = createRouter({ ...apiRoutes, ...authRoutes });

License

MIT — sirmekus (aka Emmy Boy)