@jbingen/pathparams
v0.1.0
Published
Type-safe path builder and matcher for URL templates.
Maintainers
Readme
🔗 pathparams
Type-safe path builder and matcher for URL templates.
For anyone tired of hand-rolling /users/${id}/posts/${postId} without encoding safety, missing-param checks, or type guarantees.
npm install @jbingen/pathparams// before
`/users/${id}/posts/${postId}`; // untyped, unencoded, unchecked
// after
userPost.build({ id: "1", postId: "2" }); // typed, encoded, safeParam names are inferred directly from the template string. No manual type annotations needed.
import { path } from '@jbingen/pathparams';
const userPost = path("/users/:id/posts/:postId");
userPost.build({ id: "1", postId: "2" });
// "/users/1/posts/2"
userPost.build({ id: "1" });
// compile error - missing postId
userPost.match("/users/1/posts/2");
// { id: "1", postId: "2" }
userPost.match("/other/path");
// nullWhy
Everyone hand-rolls URL construction with template literals. That works until someone forgets to encode a value, misses a param, or changes a route shape without updating every call site.
pathparams fixes all three in ~70 lines with zero dependencies. The template string is the single source of truth - TypeScript infers the rest.
API
path(template)
Creates a typed path from a URL template. Params are :name segments.
const p = path("/users/:id/posts/:postId");Returns an object with template, build, and match.
.build(params)
Builds a URL string from params. Values are stringified and URL-encoded.
p.build({ id: "1", postId: "2" }); // "/users/1/posts/2"
p.build({ id: 42, postId: 7 }); // "/users/42/posts/7"Static paths (no params) take no arguments:
const health = path("/health");
health.build(); // "/health"Throws at runtime if a param is missing. Catches it at compile time if you're using TypeScript.
.match(pathname)
Matches a pathname against the template. Returns typed params or null.
p.match("/users/1/posts/2"); // { id: "1", postId: "2" }
p.match("/users/1"); // null
p.match("/other"); // nullValues are URL-decoded. Match is exact - no partial matches, no trailing segment tolerance.
.template
The original template string, preserved as a string literal type.
p.template; // "/users/:id/posts/:postId"How types work
Param names are extracted from the template at the type level using template literal inference:
path("/users/:id/posts/:postId");
// build requires: { id: string | number | boolean, postId: string | number | boolean }
// match returns: { id: string | number | boolean, postId: string | number | boolean } | null
path("/health");
// build requires: no arguments
// match returns: {} | nullNo generics to pass manually. The template string is the source of truth.
Status
Early, but stable. The API surface is intentionally small and expected to remain mostly additive.
Design decisions
- Zero dependencies. ~70 lines of TypeScript.
- Param names inferred from template literals at compile time.
- Values are stringified, URL-encoded on build, URL-decoded on match.
- Static segments are regex-escaped for safe matching.
- No partial matching, no wildcards, no query strings. Just path params.
