UNPKG

ts-routes

Version:
257 lines (187 loc) 7.23 kB
# ts-routes [![npm](https://img.shields.io/npm/v/ts-routes)](https://www.npmjs.com/package/ts-routes) [![Actions Status](https://github.com/leancodepl/ts-routes/workflows/build/badge.svg)](https://github.com/leancodepl/ts-routes/actions) 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 ```ts 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: ```ts 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](https://github.com/ljharb/qs). ### Parameters You can define route params (i.e. parts of the path that are variable) by interpolating the `arg` function inside a segment: ```ts 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. ```ts segment`/users/${arg("userId", { optionality: "optional", pattern: "[0-9]", })}`; ``` When creating a route, path parameters can be passed in the first argument: ```ts 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. ```ts { ...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: ```ts 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. ```ts 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: ```ts 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: ```ts 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: ```ts 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](https://github.com/pillarjs/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](https://github.com/pillarjs/path-to-regexp)-style syntax (e.g. React Router DOM, Vue Router). ### React Router DOM You can use patterns for defining routes: ```tsx <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: ```ts 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: ```tsx 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: ```ts const router = new VueRouter({ routes: [ { path: routes.products.pattern, component: ProductsPage, }, ], }); ```