10.0.6 • Published 3 months ago

typesafe-routes v10.0.6

Weekly downloads
56
License
MIT
Repository
github
Last release
3 months ago

Typesafe Routes

Spices up your favorite routing library by adding type safety to plain string-based route definitions. Let typescript handle the detection of broken links in compilation time while you create maintainable software products.

You can use this utility with your favorite framework that follows path-to-regex syntax (although we only support a subset of it). You can find some demo applications with react-router or express in src/demo.

Typesafe Routes utilizes Template Literal Types and Recursive Conditional Types. These features are only available in typescript version 4.1 which is still in beta. (release date is set to November 2020)

Installation (npm/yarn examples)

npm i typesafe-routes@7.0.0-beta

# or

yarn add typesafe-routes@7.0.0-beta

Usage

route(path: string, parserMap: Record<string, Parser>, children: Record<string, ChildRoute>)

  • path: string must be the path string according to the path-to-regex syntax.
  • parserMap: Record<string, Parser> contains parameter specific parser (Parser) identified by parameter name (string)
  • children assign children routes here in case you want to utilize serialization of nested routes

Examples

import { route, stringParser } from "typesafe-routes";

const settingsRoute = route("/settings", {}, {});

const accountRoute = route("/account/:accountId", {
  accountId: stringParser, // responsible for parsing/serialization
}, {
  settingsRoute, // child route
});

const userRoute = route("/user/:userId", {}, {});
// ts error: userId parser is missing

// serialisation:

accountRoute({ accountId: "5c9f1e79e96c" }).$
// returns "/account/5c9f1e79e96c"

accountRoute({ accountId: "5c9f1e79e96c" }).settingsRoute({}).$
// returns "/account/5c9f1e79e96c/settings"

accountRoute({ accountId: 123 }).$
// ts error: accountId can't be number 

accountRoute({ }).$
// ts error: missing accountId can't be number

// parsing:

accountRoute.parseParams({ accountId: "123"})
// returns { accountId: "123"}

Besides stringParser there are also intParser, floatParser, dateParser, and booleanParser shipped with the module. The implementation of custom parsers must follow the interface Parser<T>. You can find more details on that topic further down the page.

Optional Parameters

Parameters can be suffixed with a question mark (?) to make the parameter optional.

import { route, intParser } from "typesafe-routes";

const userRoute = route("/user/:userId/:groupId?", {
  userId: intParser,
  groupId: intParser, // parser must be specified for optional parameters
}, {});

// serialisation:

userRoute({ userId: 5 }).$
// returns "/user/5"

userRoute({ userId: 7, groupId: 34 }).$
// returns "/user/7/34"

userRoute({ userId: 7, grouId: 34 }).$
// ts error: typo in "grouId"

// parsing:

userRoute.parseParams({ userId: "6", groupId: "12" })
// returns { userId: 6, groupId: 12 }

Query Parameters

Parameters can be prefixed with & to make the parameter a query parameter.

import { route, intParser } from "typesafe-routes";

const usersRoute = route("/users&:start&limit", {
  start: intParser,
  limit: intParser,
}, {});

usersRoute({ start: 10, limit: 20 }).$
// returns "/users?start=10&limit=20"

When serialising nested routes query params are always being appended:

import { route, intParser } from "typesafe-routes";

const settingsRoute = route("/settings&:expertMode", {
  expertMode: booleanParser,
}, {});

const usersRoute = route("/users&:start&limit", {
  start: intParser,
  limit: intParser,
}, {
  settingsRoute
});

usersRoute({ start: 10, limit: 20 }).settingsRoute({ expertMode: true })$
// returns "/users/settings?expertMode=true&start=10&limit=20"

userRoute.parseParams({ start: "10", limit: "20", expertMode: "false" });
// returns { start: 10, limit: 20, expertMode: false }

Custom Parser

If you need to parse/serialize other datatypes than primitive types or dates or the build-in parsers don't meet your requirements for some reason you can create your own parsers with a few lines of code. The Parser<T> interface that helps yo to achieve that is defined as followed:

interface Parser<T> {
  parse: (s: string) => T;
  serialize: (x: T) => string;
}

The next example shows how it can be imported and used to implement a typesafe Vector2D parser/serializer.

import { Parser, route } from "typesafe-routes";

interface Vector2D {
  x: number;
  y: number;
};

const vectorParser: Parser<Vector2D> = {
  serialize: (v) => btoa(JSON.stringify(v)),
  parse: (s) => JSON.parse(atob(s)),
};

const mapRoute = route("/map&:pos", { pos: vectorParser }, {});

mapRoute({ pos: { x: 1, y: 0 }}).$;
// returns "/map?pos=eyJ4IjoxLCJ5IjowfQ=="

vectorParser.parseParams({pos: "eyJ4IjoxLCJ5IjowfQ=="})
// returns { pos: { x: 1, y: 0 }}

Roadmap

So far I consider this library feature-complete that's why I will be mainly concerned about fixing bugs and improving the API. However, if some high demand for additional functionality or PRs shows up I might be considering expanding the scope.

Project Support

11.0.0-beta.1

3 months ago

11.0.0-beta.2

3 months ago

11.0.0-beta.0

3 months ago

10.0.6

6 months ago

10.0.5

9 months ago

10.0.3

12 months ago

10.0.4

12 months ago

10.0.0

1 year ago

10.0.1

1 year ago

10.0.2

1 year ago

8.0.0

2 years ago

9.0.1

2 years ago

9.0.0

2 years ago

7.0.2

3 years ago

7.0.1

3 years ago

7.0.0

3 years ago

7.0.0-beta.1

4 years ago

7.0.0-beta.0

4 years ago

7.0.0-beta

4 years ago

6.0.0

4 years ago

5.0.0

4 years ago

4.0.0

5 years ago

3.0.0

5 years ago

2.0.0

5 years ago

1.1.0

5 years ago

1.0.0

5 years ago