[ANN] routes: path based routing for web applications


I have just published an initial release for a path based routing library. The library has minimal dependencies and isn’t tied to any particular HTTP or UI framework.

It will offer the user to declare routes via the provided combinators, that in-turn extract and forward the parameters to a request handler. Since the route definitions are just simple functions, it should be straightforward to reuse and compose smaller routing pieces to construct the final routing list.

Example usage can be like below:

module Request = struct
  type t

module Response = struct
  type t

let get_user (id: int) () =

let search_user (name: string) (city : string) () =

let routes =
  let open Routes in
  [ s "" ==> fun () - ... (* matches the index route "/" *)
  ; (method' (Some `GET)) (s "user" </> int) ==> get_user (* matches "/user/<int>" *)
  ; method' None </> (s "user" </> str </> str) ==> search_user (*  matches "/user/<str>/<str>" *)

match Routes.match' routes ~target:"/some/url" ~meth:`GET =
| None -> (* No route matched. Alternative could be to provide default routes *)
| Some r -> (* Match found. Do something further with handler response *)```

Git repository: https://github.com/anuragsoni/routes

Example using Httpaf: https://github.com/anuragsoni/routes/blob/master/example/main.ml

This is not published on opam yet so it can be pinned locally via:
opam pin add routes git+https://github.com/anuragsoni/routes.git



Have you looked at dispatch ?

What are the advantages/differences of path?



Yes i did look at dispatch. One difference from dispatch is that the URL’s path parameters are extract in a way that assigns them the types defined by the user. If such a type coercion isn’t possible the route won’t match.



A small update to the library.

  • Updated the internal representation of a route so the same source is used for both scanning and printing routes.
  • Added a sprintf like function to format routes.
  • Route matching is now strict by default. ex: s "user" </> str will just match /user/<string> and not /user/<string>/*
  • Following from the previous point, nested routing has been removed for now.
utop # let route = method' None (s "foo" </> int </> str </> bool);;
- : val route : (int -> string -> bool -> unit -> '_weak1, '_weak1) route =
  Route (None, S ("foo", S ("/", Int (S ("/", Str (S ("/", Bool End)))))))

utop # sprintf route;;
- : int -> string -> bool -> unit -> string = <fun>

utop # (sprintf route) 12 "bar" false ();;
- : string = "foo/12/bar/false"