Type Safe Routing with fp-ts-routing
Tags: typescript, react, functional
Published: 2020-07-01 22:01:47

tldr;
> [`fp-ts-routing`](https://github.com/gcanti/fp-ts-routing) is a typesafe router (an alternative to [`react-router`](https://reacttraining.com/react-router/), [`@angular/router`](https://angular.io/guide/router), [`vue.js router`](https://router.vuejs.org/), etc). [video tutorial here (react)](https://www.youtube.com/watch?v=gi0ezaXc018&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK)

# Introduction

[`fp-ts-routing`](https://github.com/gcanti/fp-ts-routing) is a type safe string parsing library inspired by [purescript-routing](https://github.com/purescript-contrib/purescript-routing/blob/master/GUIDE.md). It's part of the [`fp-ts`](https://github.com/gcanti/fp-ts) (**f**unctional **p**rogramming with **t**ype **s**cript) [ecosystem](https://gcanti.github.io/fp-ts/ecosystem/).

# Motivation

### Framework Agnostic

[`fp-ts-routing`](https://github.com/gcanti/fp-ts-routing) has no dependence on the [browser](https://developer.mozilla.org/en-US/docs/Web/API). This means that it can be used with any framework that has access to the browser [`window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) (e.g. [`react`](https://github.com/microsoft/TypeScript/blob/f27ff087760fd287a0eb0e1cda9a311adaf91f9f/lib/lib.dom.d.ts#L18432), [`angular`](https://juristr.com/blog/2016/09/ng2-get-window-ref/), [`vue.js`](https://stackoverflow.com/questions/54166847/how-to-access-the-window-object-in-vue-js)).

### Prevents typos

[`react-router`](https://reacttraining.com/react-router/web/api/Route/path-string-string), [`@angular/router`](https://github.com/angular/angular/blob/master/packages/router/src/config.ts#L396) and [`vue.js router`](https://github.com/vuejs/vue-router/blob/dev/types/router.d.ts#L149) use `string` for matching route paths. They also use `string` for rerouting ([`react-router`](https://github.com/ReactTraining/history/blob/master/docs/navigation.md), [`@angular/router`](https://angular.io/api/common/Location#go), [`vue.js router`](https://github.com/vuejs/vue-router/blob/dev/types/router.d.ts#L32)). This invites typos and leads to misbehavior when these strings don't match.

[`fp-ts-routing`](https://github.com/gcanti/fp-ts-routing) parses routes into [sum types](https://jrsinclair.com/articles/2019/algebraic-data-types-what-i-wish-someone-had-explained-about-functional-programming/), which are validated at compile time. This eliminates the possibility of mismatches or misspellings.

### Pattern Matching

With routes represented as [sum types](https://jrsinclair.com/articles/2019/algebraic-data-types-what-i-wish-someone-had-explained-about-functional-programming/), it's easy to render according to a [pattern match](https://github.com/sledorze/morphic-ts#matchers).

This gives users [exhaustiveness checking](https://www.typescriptlang.org/docs/handbook/advanced-types.html#exhaustiveness-checking) - a helpful compile time error if users fail to render every route. 

### Parses Route Params to Arbitrary Types

[`react-router`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-router/index.d.ts#L164), [`@angular/router`](https://github.com/angular/angular/blob/1d5ca55aab6d49eecbb19e84739f7178b52a64ed/packages/router/src/shared.ts#L27) and [`vue.js router`](https://github.com/vuejs/vue-router/blob/f0d9c2d93cf06b5b2202bd77df6281cf47970b20/types/router.d.ts#L143) all use `string` for route params and queries.

[`fp-ts-routing`](https://github.com/gcanti/fp-ts-routing) allows users to parse any type they want for route params and [queries](https://github.com/gcanti/fp-ts-routing/blob/1a3c7b030278696d45f9f948b8d7d032d13866aa/test/index.ts#L198) - [strings](https://github.com/gcanti/fp-ts-routing/blob/1a3c7b030278696d45f9f948b8d7d032d13866aa/test/index.ts#L186), [integers](https://github.com/gcanti/fp-ts-routing/blob/1a3c7b030278696d45f9f948b8d7d032d13866aa/test/index.ts#L191), [anything really](https://github.com/gcanti/fp-ts-routing/blob/1a3c7b030278696d45f9f948b8d7d032d13866aa/test/index.ts#L174) (by way of [`io-ts`](https://github.com/gcanti/io-ts/blob/master/index.md#implemented-types--combinators))

### Accessibility (React)

Because [`react-router`](https://reacttraining.com/react-router/)'s [`<Link>`](https://reacttraining.com/react-router/web/api/Link) [composes](https://reactjs.org/docs/composition-vs-inheritance.html#specialization) [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a), [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) (included in [create-react-app](https://reactjs.org/docs/accessibility.html#eslint-plugin-jsx-a11y)) is unable to provide [`<Link>`](https://reacttraining.com/react-router/web/api/Link) users proper [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) warnings ([validity](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-is-valid.md), [content](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/anchor-has-content.md)). Rather than [enforce these warnings statically](https://github.com/ReactTraining/react-router/pull/7435), the [`react-router`](https://reacttraining.com/react-router/) team made the (understandable) decision to [ignore them](https://github.com/ReactTraining/react-router/blob/84252237e86930a6c78edd7621a6dbd9516c1566/packages/react-router-dom/modules/Link.js#L62).

Because [`fp-ts-routing`](https://github.com/gcanti/fp-ts-routing) has no dependence on the DOM, users are able to use plain [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) tags, which can give the proper accessibility warnings.

### Simplicity & Clarity of Purpose

[`fp-ts-routing`](https://github.com/gcanti/fp-ts-routing) does very little - it can [parse](https://github.com/gcanti/fp-ts-routing/blob/1a3c7b030278696d45f9f948b8d7d032d13866aa/test/index.ts#L52) a string to a [sum type](https://jrsinclair.com/articles/2019/algebraic-data-types-what-i-wish-someone-had-explained-about-functional-programming/), and [back again](https://github.com/gcanti/fp-ts-routing/blob/1a3c7b030278696d45f9f948b8d7d032d13866aa/test/index.ts#L110). In this author's opinion, this makes it easier to learn than, for example, [`react-router`](https://reacttraining.com/react-router/web/guides/quick-start), which has [4 hooks](https://reacttraining.com/react-router/web/api/Hooks), an [HOC](https://reacttraining.com/react-router/web/api/withRouter),  [11 components](https://reacttraining.com/react-router/web/api/HashRouter), [esoteric](https://reacttraining.com/react-router/web/api/Route/exact-bool) and [strange](https://reacttraining.com/react-router/web/api/Switch) matching logic, and [`many`](https://reacttraining.com/react-router/web/api/Route/render-func) [`distinct`](https://reacttraining.com/react-router/web/api/Route/children-func) [`but`](https://reacttraining.com/react-router/web/api/Hooks/useroutematch) [`similar`](https://reacttraining.com/react-router/web/api/Hooks/useparams) [`usages`](https://reacttraining.com/react-router/web/api/Hooks/uselocation) [`of route params`](https://reacttraining.com/react-router/web/api/Hooks/usehistory).

Usage of [`fp-ts-routing`](https://github.com/gcanti/fp-ts-routing) requires learning about [sum types](https://jrsinclair.com/articles/2019/algebraic-data-types-what-i-wish-someone-had-explained-about-functional-programming/) and the [browser window api](https://developer.mozilla.org/en-US/docs/Web/API/Window). It can be an interesting case study for [io-ts](https://github.com/gcanti/io-ts). These are fundamentals, and in this author's opinion, learning about them is more fruitful than learning a routing framework's arbitrary rules.

# [Video Tutorial](https://www.youtube.com/watch?v=gi0ezaXc018&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK)

I've put together an [`fp-ts-routing` video tutorial](https://www.youtube.com/watch?v=gi0ezaXc018&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK) (38 min). It's for beginners, so don't worry, you don't need to know anything about [fp-ts](https://github.com/gcanti/fp-ts) or [io-ts](https://github.com/gcanti/io-ts). If you know [basic typescript](https://www.typescriptlang.org/) and [basic react](https://reactjs.org/) you'll be able to follow along.

The tutorial covers:
1. [`window.location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location) & [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) ([video](https://www.youtube.com/watch?v=nJqpAw0D26c&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=3&t=2s))
2. [`window.history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) & [`popstate`](https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate) ([video](https://www.youtube.com/watch?v=hOfFmViHatE&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=3))
3. [union types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types) ([video](https://www.youtube.com/watch?v=mQ3UGU8WvaU&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=4))
4. [tagged union types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions) ([video](https://www.youtube.com/watch?v=sG_YfblFZjM&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=5))
5. [`fp-ts-routing`](https://github.com/gcanti/fp-ts-routing) ([video](https://www.youtube.com/watch?v=PjulzgAgJg0&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=7))
6. [`@morphic-ts/adt`](https://github.com/sledorze/morphic-ts#adt-manipulation) ([video](https://www.youtube.com/watch?v=QwIBRKLnGhI&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=10))
7. [`morphic-ts-routing`](https://github.com/anthonyjoeseph/morphic-ts-routing) ([video](https://www.youtube.com/watch?v=2JGnduPPq-E&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=11))

There are also bonus videos that cover:
- [`window.location.hash`](https://developer.mozilla.org/en-US/docs/Web/API/Location/hash) & [history state](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) ([video](https://www.youtube.com/watch?v=GghyKebWfUI&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=5&t=4s))
- [`fp-ts-routing` param & query types](https://github.com/gcanti/fp-ts-routing/blob/1a3c7b030278696d45f9f948b8d7d032d13866aa/test/index.ts#L174) ([video](https://www.youtube.com/watch?v=Y0RdRxLyzTY&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=9&t=0s))
- [`@morphic-ts/adt` `verified`](https://github.com/sledorze/morphic-ts/blob/ae5fa8dbece8bd6aca363a617a30d9a10229ed37/packages/morphic-adt/test/adt.spec.ts#L250) ([video](https://www.youtube.com/watch?v=K1rHHLoVQpQ&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=11&t=2s))
- a brief history of typescript adt libraries ([video](https://www.youtube.com/watch?v=vxhE0cL04GM&list=PLDQLXeWSQx3-slUe20drQnfi28nLvBpiK&index=12&t=0s))

The tutorial is free. Please consider donating if you'd like to support [future planned tutorials](https://www.buymeacoffee.com/typesaferouting).

If you are an experienced developer and you'd prefer to skip the tutorial, here's code for an [example application](https://github.com/anthonyjoeseph/fp-ts-routing-examples/blob/master/src/examples/1-react-router/7-morphic-ts-routing/App.tsx) that mirrors [this `react-router` example app](https://reacttraining.com/react-router/web/guides/quick-start/2nd-example-nested-routing).