[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
  ...
end

module Response = struct
  type t
  ...
end

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

10 Likes

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"

Few more updates:

  • This is now available on opam to make it easy to try it out (http://opam.ocaml.org/packages/routes/)
  • Internally routes are now grouped on the HTTP methods wherever possible
  • The combinators translate the route definitions into a trie (this allowed to share the prefix matching on all routes)
  • A little bit of route re-writing is done to avoid un-necessary nested skip/apply actions.
  • I added an example about how this can be used as an Opium middleware.

I have given up on some features from before (removed route printing, and nested routing are now removed)

Please report issues/problems you notice if you decide to try it out :slight_smile:

1 Like