All Projects → anuragsoni → Routes

anuragsoni / Routes

Licence: bsd-3-clause
typed bidirectional routes for OCaml/ReasonML web applications

Programming Languages

ocaml
1615 projects
reason
219 projects
bucklescript
41 projects

Projects that are alternatives of or similar to Routes

Literoute
LiteRoute is easy transition for your app. Written on Swift 4
Stars: ✭ 90 (-11.76%)
Mutual labels:  router
Phprouter
PhpRouter is a powerful, minimal, and very fast HTTP URL router for PHP projects
Stars: ✭ 97 (-4.9%)
Mutual labels:  router
Reason
Simple, fast & type safe code that leverages the JavaScript & OCaml ecosystems
Stars: ✭ 9,545 (+9257.84%)
Mutual labels:  reasonml
Chi
lightweight, idiomatic and composable router for building Go HTTP services
Stars: ✭ 10,581 (+10273.53%)
Mutual labels:  router
Min
A minimalistic web framework with route grouping and middleware chaining
Stars: ✭ 95 (-6.86%)
Mutual labels:  router
Ppx bs css
A ppx rewriter for CSS expressions.
Stars: ✭ 98 (-3.92%)
Mutual labels:  reasonml
Bone
Lightning Fast HTTP Multiplexer
Stars: ✭ 1,270 (+1145.1%)
Mutual labels:  router
Pc Engines Apu Router Guide
Guide to building a Linux or BSD router on the PC Engines APU platform
Stars: ✭ 101 (-0.98%)
Mutual labels:  router
Fast Path Lede Openwrt
PLEASE GO TO NEW OPENWRT TRUNK BASED SFE FIRMWARE ->
Stars: ✭ 96 (-5.88%)
Mutual labels:  router
Routersploit
Exploitation Framework for Embedded Devices
Stars: ✭ 9,866 (+9572.55%)
Mutual labels:  router
A Reason React Tutorial
included code for A ReasonReact Tutorial
Stars: ✭ 94 (-7.84%)
Mutual labels:  reasonml
React Router Scroll Memory
React component to keep the scroll of the page and to restore it if the user clicks on the back button of its browser
Stars: ✭ 95 (-6.86%)
Mutual labels:  router
Reason React Native Web Example
Razzle + Reason-React + React-Native-Web. Damn that's a lot of R's.
Stars: ✭ 98 (-3.92%)
Mutual labels:  reasonml
Go Tgbot
Golang telegram bot API wrapper, session-based router and middleware
Stars: ✭ 90 (-11.76%)
Mutual labels:  router
Iceworld
tonado的multi-thread 多线程封装
Stars: ✭ 99 (-2.94%)
Mutual labels:  router
Reason Loadable
🔥 Suspense/Lazy for ReasonReact.
Stars: ✭ 88 (-13.73%)
Mutual labels:  reasonml
Twig
Twig - less is more's web server for golang
Stars: ✭ 98 (-3.92%)
Mutual labels:  router
Xunleikuainiaoinshell
[ 迅雷快鸟 Shell 版 ] A Shell Implementation of Kuainiao, Xunlei
Stars: ✭ 102 (+0%)
Mutual labels:  router
Jsoo React
js_of_ocaml bindings for ReactJS. Based on ReasonReact.
Stars: ✭ 101 (-0.98%)
Mutual labels:  reasonml
Dotweb
Simple and easy go web micro framework
Stars: ✭ 1,354 (+1227.45%)
Mutual labels:  router

Routes   BuildStatus Coverage Status

This library will help with adding typed routes to OCaml applications. The goal is to have a easy to use portable library with reasonable performance See benchmark folder.

Users can create a list of routes, and handler function to work on the extracted entities using the combinators provided by the library. To perform URL matching one would just need to forward the URL's path to the router.

Demo

You can follow along with these examples in the OCaml toplevel (repl). down or utop are recommended to enhance your REPL experience while working through these examples. They will add autocompletion support which can be useful when navigating a new library.

We will start by setting up the toplevel by asking it to load routes.

# #require "routes";;

We will start by defining a few simple routes that don't need to extract any path parameter.

# (* A simple route that matches the empty path segments. *)
# let root () = Routes.empty;;
val root : unit -> ('a, 'a) Routes.target = <fun>

# (* We can combine multiple segments using `/` *)
# let users () = Routes.(s "users" / s "get" /? nil);;
val users : unit -> ('a, 'a) Routes.target = <fun>

We can use these route definitions to pretty-print into "patterns" that can potentially be used to show what kind of routes your application can match. An application could potentially use this as a response to a route-not-found error and inform the client of what kind of routes it supports. We will use Format.asprintf to get a string that contains the result of our pretty printer.

# Format.asprintf "%a" Routes.pp_target (root ());;
- : string = "/"

# Format.asprintf "%a" Routes.pp_target (users ());;
- : string = "/users/get"

Matching routes where we don't need to extract any parameter could be done with a simple string match. The part where routers are useful is when there is a need to extract some parameters are extracted from the path.

# let sum () = Routes.(s "sum" / int / int /? nil);;
val sum : unit -> (int -> int -> 'a, 'a) Routes.target = <fun>

Looking at the type for sum we can see that our route knows about our two integer path parameters. A route can also extract parameters of different types.

# let get_user () = Routes.(s "user" / str / int64 /? nil);;
val get_user : unit -> (string -> int64 -> 'a, 'a) Routes.target = <fun>

We can still pretty print such routes to get a human readable "pattern" that can be used to inform someone what kind of routes are defined in an application.

Once we start working with routes that extract path parameters, there is another operation that can sometimes be useful. Often times there can be a need to generate a URL from a route. It could be for creating hyperlinks in HTML pages, creating target URLs that can be forwarded to HTTP clients, etc.

Using routes we can create url targets from the same type definition that is used for performing a route match. Using this approach for creating url targets has the benefit that whenever a route definition is updated, the printed format for the url target will also reflect that change. If the types remain the same, then the printing functions will automatically start generating url targets that reflect the change in the route type, and if the types change the user will get a compile time error about mismatched types. This can be useful in ensuring that we avoid using bad/outdated URLs in our application.

# Format.asprintf "%a" Routes.pp_target (sum ());;
- : string = "/sum/:int/:int"

# Format.asprintf "%a" Routes.pp_target (get_user ());;
- : string = "/user/:string/:int64"

# Routes.sprintf (sum ());;
- : int -> int -> string = <fun>

# Routes.sprintf (get_user ());;
- : string -> int64 -> string = <fun>

# Routes.sprintf (sum ()) 45 12;;
- : string = "/sum/45/12"

# Routes.sprintf (sum ()) 11 56;;
- : string = "/sum/11/56"

# Routes.sprintf (get_user ()) "JohnUser" 1L;;
- : string = "/user/JohnUser/1"

# Routes.sprintf (get_user ()) "foobar" 56121111L;;
- : string = "/user/foobar/56121111"

We've seen a few examples so far, but none of any actual routing. Before we can perform a route match, we need to connect a route definition to a handler function that gets called when a successful match happens.

# let sum_route () = Routes.(sum () @--> fun a b -> Printf.sprintf "%d" (a + b));;
val sum_route : unit -> string Routes.route = <fun>

# let user_route () = Routes.(get_user () @--> fun name id -> Printf.sprintf "(%Ld) %s" id name);;
val user_route : unit -> string Routes.route = <fun>

# let root () = Routes.(root () @--> "Hello World");;
val root : unit -> string Routes.route = <fun>

Now that we have a collection of routes connected to handlers, we can create a router and perform route matching. Something to keep in mind is that we can only combine routes that have the same final return type, i.e. handlers attached to every route in a router should have the same type for the values they return.

# let routes = Routes.one_of [sum_route (); user_route (); root ()];;
val routes : string Routes.router = <abstr>

# Routes.match' routes ~target:"/";;
- : string option = Some "Hello World"

# Routes.match' routes ~target:"/sum/25/11";;
- : string option = Some "36"

# Routes.match' routes ~target:"/user/John/1251";;
- : string option = Some "(1251) John"

# Routes.match' routes ~target:(Routes.sprintf (sum ()) 45 11);;
- : string option = Some "56"

# (* This route fails to match because of the final trailing slash. *)
# Routes.match' routes ~target:"/sum/1/2/";;
- : string option = None

Dealing with trailing slashes

Every route definition can control what behavior it expects when it encounters a trailing slash. In the examples above all route definitions ended with /? nil. This will result in a successful match if the route does not end in a trailing slash.

# let no_trail () = Routes.(s "foo" / s "bar" / str /? nil @--> fun msg -> String.length msg);;
val no_trail : unit -> int Routes.route = <fun>

# Routes.(match' (one_of [ no_trail () ]) ~target:"/foo/bar/hello");;
- : int option = Some 5

# Routes.(match' (one_of [ no_trail () ]) ~target:"/foo/bar/hello/");;
- : int option = None

To create a route that returns a success if there is a trailing slash, the route still needs to end with nil, but instead of /?, //? needs to be used. (note the extra slash).

# let trail () = Routes.(s "foo" / s "bar" / str //? nil @--> fun msg -> String.length msg);;
val trail : unit -> int Routes.route = <fun>

# Routes.(match' (one_of [ trail () ]) ~target:"/foo/bar/hello");;
- : int option = None

# Routes.(match' (one_of [ trail () ]) ~target:"/foo/bar/hello/");;
- : int option = Some 5

More example of library usage can be seen in the examples folder, and as part of the test definition.

Installation

To use the version published on opam:
opam install routes
For development version:
opam pin add routes git+https://github.com/anuragsoni/routes.git

Related Work

The combinators are influenced by Rudi Grinberg's wonderful blogpost about type safe routing done via an EDSL using GADTs + an interpreted for the DSL.

Also thanks to Gabriel Radanne for feedback and for the blog post showing the technique used in printf like functions.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].