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

@caseyplummer/ts-route

v1.0.6

Published

Route definitions in TypeScript

Readme

ts-route

Type-safe route definitions and navigation utilities for TypeScript applications.

Features

  • 🎯 Type-safe route definitions with TypeScript generics
  • 🔗 Dynamic parameter extraction from URL patterns
  • 📝 Query parameter parsing with validation
  • 🌳 Nested route support with breadcrumb trails
  • 🔄 Dual module support (ESM + CommonJS)
  • Zero runtime dependencies
  • 📦 Tree-shakeable exports
  • Route defaults with app-specific configuration
  • 🔧 Custom encoders for app-specific data types
  • 📊 Enhanced query parsing for app-specific data types
  • 🧪 Comprehensive testing with 320+ test cases

Installation

npm install @caseyplummer/ts-route
# or
pnpm add @caseyplummer/ts-route
# or
yarn add @caseyplummer/ts-route

Quick Start

1. Define Your Routes

import { Route as RouteBase, WildcardRoute } from '@caseyplummer/ts-route';
import { getHref } from './app-helpers';
import { AppQueryParams } from './app-query';
import { applyAppRouteDefaults } from './app-route-defaults';

// Define route paths as enums for type safety
export enum RoutePath {
  Home = '',
  Register = 'register',
  SignIn = 'sign-in',
  Dashboard = '@[handle]',
  Profile = '@[handle]/profile',
  Posts = 'posts',
  Post = 'post/[id]',
}

// Query types for specific route(s)
export interface SignInQuery {
  redirect?: string;
  error?: string;
}

// Metadata types for specific route(s)
export interface SidebarMeta {
  hasSidebar: boolean;
}

// Context types for specific route(s)
export interface IdNameContext {
  id: string;
  name: string;
}

// Narrow the Route type with an app default
export type Route<
  Path extends RoutePath,
  Query extends object = object,
  Meta extends object = object,
  Context extends object = object,
> = RouteBase<Path, AppQueryParams, Query, Meta, Context>;

// Define route types with full type safety
export type HomeRoute = Route<RoutePath.Home>;
export type RegisterRoute = Route<RoutePath.Register>;
export type SignInRoute = Route<RoutePath.SignIn, SignInQuery>;
export type DashboardRoute = Route<RoutePath.Dashboard>;
export type ProfileRoute = Route<RoutePath.Profile>;
export type PostsRoute = Route<RoutePath.Posts>;
export type PostRoute = Route<RoutePath.Post, object, object, IdNameContext>;

// Union type for all defined routes
export type AppRoute = HomeRoute | RegisterRoute | SignInRoute | DashboardRoute | ProfileRoute | PostsRoute | PostRoute;

// Centralized route definitions
export const baseRoutes: AppRoute[] = [
  {
    path: RoutePath.Home,
    title: () => 'Home',
  },
  {
    path: RoutePath.Dashboard,
    title: ({ params }) => `${params?.handle}'s Dashboard`,
  },
  {
    path: RoutePath.Profile,
    parentPath: RoutePath.Dashboard,
    title: ({ params }) => `${params?.handle}'s Profile`,
  },
  {
    path: RoutePath.SignIn,
    title: () => 'Sign In',
    getQuery: (params) => ({
      redirect: params.value('redirect'),
      error: params.value('error'),
    }),
  },
  {
    path: RoutePath.Register,
    title: () => 'Register',
  },
  {
    path: RoutePath.Posts,
    title: () => 'Posts',
  },
  {
    path: RoutePath.Post,
    title: ({ params, context }) => context?.name ?? `Post ID ${params?.id}`,
    breadcrumb: ({ params, context }) => context?.name ?? `Post ID ${params?.id}`,
    href: ({ params, context }) => {
      const base = `/posts/${params?.id}`;
      return context?.name ? `${base}#${context.name}` : base;
    },
  },
] as const;

export const appRoutes: AppRoute[] = applyAppRouteDefaults(baseRoutes);

// Provides better syntax for using routes in components
export const routes = {
  home: { href: () => getHref(RoutePath.Home) },
  dashboard: {
    href: (handle: string) => getHref(RoutePath.Dashboard, { params: { handle } }),
  },
  profile: {
    href: (handle: string) => getHref(RoutePath.Dashboard, { params: { handle } }),
  },
  signIn: {
    href: (redirect?: string, error?: string) => getHref(RoutePath.SignIn, { query: { redirect, error } }),
  },
  register: { href: () => getHref(RoutePath.Register, {}) },
  posts: { href: () => getHref(RoutePath.Posts) },
  post: {
    href: (id: string, name?: string) => getHref(RoutePath.Post, { context: { id, name } }),
  },
};

2. Use Routes in Components

React Example

import { Link, useLocation } from 'react-router-dom';
import { findRoute } from '@caseyplummer/ts-route';
import { appRoutes, routes } from './routes';

function Navigation() {
  const location = useLocation();
  const currentUser = { profile: { handle: 'john-doe' } }; // AuthState type

  // Find the current route with full type safety
  const currentRoute = findRoute(location.pathname, appRoutes);

  return (
    <nav>
      {/* Clean, readable syntax */}
      <Link to={routes.home.href()}>Home</Link>
      <Link to={routes.dashboard.href(currentUser)}>Dashboard</Link>
      <Link to={routes.account.href(currentUser)}>Account</Link>
      <Link to={routes.profile.href(currentUser)}>Profile</Link>
      <Link to={routes.posts.href()}>Posts</Link>
      <Link to={routes.post.href('123', 'My Post')}>View Post</Link>

      {/* Auth routes with query parameters */}
      <Link to={routes.signIn.href()}>Sign In</Link>
      <Link to={routes.verifyEmail.href(true)}>Verify Email</Link>
      <Link to={routes.forgotPassword.href('[email protected]')}>Forgot Password</Link>

      {/* Display current page title */}
      {currentRoute && <h1>{currentRoute.title({})}</h1>}
    </nav>
  );
}

Vue Example

<template>
  <nav>
    <router-link :to="routes.home.href()">Home</router-link>
    <router-link :to="routes.dashboard.href(currentUser)">Dashboard</router-link>
    <router-link :to="routes.account.href(currentUser)">Account</router-link>
    <router-link :to="routes.profile.href(currentUser)">Profile</router-link>
    <router-link :to="routes.posts.href()">Posts</router-link>
    <router-link :to="routes.post.href('123', 'My Post')">View Post</router-link>

    <h1 v-if="currentTitle">{{ currentTitle }}</h1>
  </nav>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { findRoute } from '@caseyplummer/ts-route';
import { appRoutes, routes } from './routes';

const route = useRoute();
const currentUser = { profile: { handle: 'john-doe' } }; // AuthState type

const currentTitle = computed(() => {
  const matched = findRoute(route.path, appRoutes);
  return matched?.title({});
});
</script>

Svelte Example

<script lang="ts">
  import { findRoute } from "@caseyplummer/ts-route";
  import { appRoutes, routes } from "./routes";
  import { page } from "$app/stores";

  let currentUser = { profile: { handle: "john-doe" } }; // AuthState type

  let currentRoute = $derived(findRoute($page.url.pathname, appRoutes));
</script>

<nav>
  <p><a href={routes.home.href()}>Home</a></p>
  <p><a href={routes.dashboard.href(currentUser)}>Go to Dashboard</a></p>
  <p><a href={routes.account.href(currentUser)}>Go to Account</a></p>
  <p><a href={routes.profile.href(currentUser)}>Go to Profile</a></p>
  <p><a href={routes.posts.href()}>View Posts</a></p>
  <p><a href={routes.post.href("123", "My Post")}>View Post</a></p>

  {#if currentRoute}
    <h1>{currentRoute.title({})}</h1>
  {/if}
</nav>

Vanilla JavaScript/TypeScript

import { findRoute } from '@caseyplummer/ts-route';
import { appRoutes, routes } from './routes';

// Find current route
const currentPath = window.location.pathname;
const currentRoute = findRoute(currentPath, appRoutes);

if (currentRoute) {
  console.log('Current page:', currentRoute.title({}));
  console.log('Route params:', currentRoute.params);
  console.log('Query params:', currentRoute.query);
}

// Navigate programmatically with different parameter types
function navigateToDashboard(user: AuthState) {
  window.location.href = routes.dashboard.href(user);
}

function navigateToPost(id: string, name?: string) {
  window.location.href = routes.post.href(id, name);
}

// Build URLs for links with various parameter combinations
document.querySelector('#posts-link')?.setAttribute('href', routes.posts.href());
document.querySelector('#post-link')?.setAttribute('href', routes.post.href('123', 'My Post'));
document.querySelector('#sign-in-link')?.setAttribute('href', routes.signIn.href());
document.querySelector('#verify-email-link')?.setAttribute('href', routes.verifyEmail.href(true));

3. Advanced Features

Route Defaults

import { applyRouteDefaults } from '@caseyplummer/ts-route';

// Apply defaults to all routes
const appRoutes = applyRouteDefaults(baseRoutes, {
  queryParamsFactory: (raw) => new AppQueryParams(raw),
  encodeQueryValue: (value) => String(value),
  serializeQuery: (query, args) => buildQueryString(query),
});

Custom Value Encoding

import { encodeValue } from '@caseyplummer/ts-route';

// Custom encoding for your app's data types
function appEncodeValue(value: unknown): string {
  if (value instanceof Date) {
    return encodeURIComponent(value.toISOString());
  }
  return encodeValue(value);
}

Enhanced Query Parameter Parsing

import { QueryParamsBase } from '@caseyplummer/ts-route';

class AppQueryParams extends QueryParamsBase {
  // Parse dates from ISO strings
  date(key: string): Date | undefined {
    const value = this.value(key);
    return value ? new Date(value) : undefined;
  }

  // Parse enums with case-insensitive matching
  enumValue<T>(enumObj: T, key: string): T[keyof T] | undefined {
    const value = this.value(key);
    return this.findEnumValue(enumObj, value);
  }

  // Parse boolean values
  boolean(key: string): boolean | undefined {
    const value = this.value(key);
    if (value === 'true') return true;
    if (value === 'false') return false;
    return undefined;
  }

  // Custom parsing for your app's query parameters
  userId(): number | undefined {
    const value = this.value('userId');
    return value ? parseInt(value, 10) : undefined;
  }

  tags(): string[] {
    return this.values('tag'); // Handles multiple values
  }
}

Nested Routes with Breadcrumbs

import { buildBreadcrumbTrail } from '@caseyplummer/ts-route';

// Build breadcrumb navigation
const breadcrumbs = buildBreadcrumbTrail('/john-doe/profile/settings', appRoutes);

// breadcrumbs = ['Home', "john-doe's Dashboard", "john-doe's Profile", 'Settings']

API Reference

Core Types

  • Route<Path, Query, Meta, Context> - Route definition interface
  • MatchedRoute<TRoute> - Result of route matching
  • RouteArgs<TRoute> - Arguments for route functions

Main Functions

  • findRoute(url, routes) - Find matching route for a URL
  • buildHref(route, args) - Build href for a route with parameters
  • parseUrl(url, path) - Parse URL against a specific route path
  • buildBreadcrumbTrail(url, routes) - Build breadcrumb navigation
  • applyRouteDefaults(routes, options) - Apply route defaults
  • serializeQuery(query, args, route) - Custom query serialization

Query Parameter Utilities

  • QueryParamsBase - Base class for custom query parameter parsing

Examples

Check out the examples/ directory for complete working examples:

  • app-routes.ts - Full route definitions with type safety
  • app-helpers.ts - Custom helper functions
  • app-query.ts - Extended query parameter parsing
  • app-encoders.ts - Custom value encoding
  • app-route-defaults.ts - Framework-level defaults
  • app-validation.ts - Route validation utilities

License

MIT