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 🙏

© 2024 – Pkg Stats / Ryan Hefner

ts-routes

v2.0.0

Published

Strongly typed routes management

Downloads

12,761

Readme

ts-routes

npm Actions Status

Helper library for constructing strongly typed parameterized routing paths. It prevents you from passing hardcoded strings with routes across the app.

ts-routes is independent on routing libraries and can be used together with e.g. React Router DOM or Vue Router.

Installation

npm install ts-routes
yarn add ts-routes

Quick start

import { createRouting, number, query, segment, uuid } from 'ts-routes';

const routes = createRouting({
    products: segment`/products`,
    users: segment`/users/${number('userId')}`,
    items: {
        ...segment`/items`,
        query: {
            filter: query()
        }
        children: {
            item: segment`/${uuid('itemId')}`,
        },
    },
});

routes.products(); // '/products'
routes.products.pattern // '/products'

routes.users({ userId: '10' }) // '/users/10'
routes.users.pattern // '/users/:userId([0-9]+)

routes.items({}, { filter: 'new' }) // '/items?filter=new'
routes.items.pattern // '/items'

routes.items.item({ itemId: '12d66718-e47c-4a2a-ad5b-8897def2f6a7' }) // '/items/12d66718-e47c-4a2a-ad5b-8897def2f6a7'
routes.items.item.pattern // `/items/:itemId(${uuidRegex})`

Usage

Routing

To use strongly typed paths, you first have to create the routing object by calling createRouting and providing an object defining segments. Segments represent single routing paths and are implemented as tagged template literals:

const routes = createRouting({
    users: segment`/users`
});

Second parameter to createRouting is qs configuration. You can extend/alter ts-routes functionality by providing configuration to qs. For example you can change array formatting and delimiter. For details on configuration please refer to qs documentation.

Parameters

You can define route params (i.e. parts of the path that are variable) by interpolating the arg function inside a segment:

segment`/users/${arg("userId")}`;

This will enable you to create paths like /users/1 or /users/username.

By default route parameters are treated as required. You can make them optional by providing extra configuration. It is also possible to limit possible parameter values by passing a regex string. While trying to create a route which doesn't satisfy the pattern, an exception will be thrown.

segment`/users/${arg("userId", {
    optionality: "optional",
    pattern: "[0-9]",
})}`;

When creating a route, path parameters can be passed in the first argument:

routes.users({ userId: "10" });

There are some predefined convenience parameter types provided:

  • string(name: string, optionality?: "optional" | "required" = "required") for plain strings
  • number(name: string, optionality?: "optional" | "required" = "required") for number strings
  • uuid(name: string, optionality?: "optional" | "required" = "required") for UUID strings

Query string

Query string parameters can be specified by adding query property to the route description. The query function expects an object where keys are names of parameters and values specify whether those params are required in the path.

{
    ...segment`/product`,
    query: {
        productId: query("required"),
        details: query("optional")
    }
}

The above segment defines a path which expects the productId URL param and the optional details URL param.

When creating a route query strings can be passed in the second argument:

routes.products(
    {},
    {
        productId: "10",
        details: "false",
    },
);

which will return /product?details=false&productId=10.

qs by default supports also objects and arrays when stringifying and parsing query string.

const parameters = routes.products.parseQuery(queryString)

// this will yield given parameters with given type

type Parameters = {
    productId: string | string[],
    details?: string | string[],
}

For objects you need to specify your value type when defining routes:

import { createRouting, number, query, uuid } from 'ts-routes';

type ProductDetails = { name: string, price: string };

const routes = createRouting({
    products: {
        ...segment`/product`,
        query: {
            productId: query("required"),
            details: query<ProductDetails, "optional">("optional")
        }
    }
});

const parameters = routes.products.parseQuery(queryString)

// which will yield

type Parameters = {
    productId: string | string[],
    details?: ProductDetails | ProductDetails[],
}

Additionaly you can override qs stringify and parse option directly on each route:

    routes.products(undefined, { productId: "10" }, overrideStringifyOptions);

    routes.products.parse(queryString, overrideParseOptions);

Nested routes

Routes can be nested by providing an optional children property to segments:

const routes = createRouting({
    parent: {
        ...segment`/parent`,
        children: {
            child: segment`/child`,
        },
    },
} as const);

Child routes are attached to the parent route object so that to construct a child route you can call routes.parent.child() (which will return /parent/child).

Routes can be deeply nested and child routes will include all required and optional route parameters and query string parameters from parent routes.

Patterns

While creating a routing, alongside path string generators, patterns for those paths compatible with path-to-regexp are generated. You can access them via the pattern property:

routes.products.pattern

Those patterns are useful for integration with routing libraries which support path-to-regexp-style syntax (e.g. React Router DOM, Vue Router).

React Router DOM

You can use patterns for defining routes:

<Route exact component={ProductsPage} path={routes.products.pattern} />

With React it's also useful to add some helper types which can be used for typing routing props for components:

import { FunctionComponent } from "react";
import { RouteComponentProps } from "react-router-dom";
import { PathParamsFor } from "ts-routes";

type PageProps<TPathParams extends (...args: any[]) => string> = RouteComponentProps<PathParamsFor<TPathParams>>;

type PageComponent<TPathParams extends (...args: any[]) => string> = FunctionComponent<PageProps<TPathParams>>;

Which you can then use like so:

type ProductsPageProps = PageProps<typeof routes.products>;

const ProductPage: PageComponent<typeof routes.products> = ({
    match: {
        params: { productId },
    },
}) => <div>{productId}</div>;

Vue Router

You can use patterns for defining routes:

const router = new VueRouter({
    routes: [
        {
            path: routes.products.pattern,
            component: ProductsPage,
        },
    ],
});