{"id":13726254,"url":"https://github.com/anuragsoni/routes","last_synced_at":"2025-04-07T07:17:38.692Z","repository":{"id":34465180,"uuid":"178964795","full_name":"anuragsoni/routes","owner":"anuragsoni","description":"typed bidirectional router for OCaml/ReasonML web applications","archived":false,"fork":false,"pushed_at":"2024-09-18T00:22:57.000Z","size":2769,"stargazers_count":148,"open_issues_count":0,"forks_count":11,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-03-31T05:07:20.263Z","etag":null,"topics":["bidirectional","http-router","http-routing","ocaml","router"],"latest_commit_sha":null,"homepage":"https://anuragsoni.github.io/routes/","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anuragsoni.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-04-01T23:53:03.000Z","updated_at":"2025-03-16T15:46:04.000Z","dependencies_parsed_at":"2023-12-14T03:22:13.500Z","dependency_job_id":"45ae7b39-9a2b-4392-ab38-78e29a74c7b6","html_url":"https://github.com/anuragsoni/routes","commit_stats":{"total_commits":236,"total_committers":15,"mean_commits":"15.733333333333333","dds":0.0847457627118644,"last_synced_commit":"59bc7ec831e01199e07d9cf86d77304f829ef7b1"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anuragsoni%2Froutes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anuragsoni%2Froutes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anuragsoni%2Froutes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anuragsoni%2Froutes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anuragsoni","download_url":"https://codeload.github.com/anuragsoni/routes/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247608160,"owners_count":20965953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bidirectional","http-router","http-routing","ocaml","router"],"created_at":"2024-08-03T01:02:57.021Z","updated_at":"2025-04-07T07:17:38.664Z","avatar_url":"https://github.com/anuragsoni.png","language":"OCaml","funding_links":[],"categories":["Web Development","OCaml"],"sub_categories":[],"readme":"# Routes \u0026nbsp; ![BuildStatus](https://github.com/anuragsoni/routes/workflows/RoutesTest/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/anuragsoni/routes/badge.svg?branch=master)](https://coveralls.io/github/anuragsoni/routes?branch=master)\n\nThis library will help with adding typed routes to OCaml applications.\nThe goal is to have a easy to use portable library with\nreasonable performance ([See benchmark folder](./bench/)).\n\nUsers can create a list of routes, and handler function to work\non the extracted entities using the combinators provided by\nthe library. To perform URL matching one would just need to forward\nthe URL's path to the router.\n\n## Demo\n\nYou can follow along with these examples in the OCaml toplevel (repl).\n[down](https://github.com/dbuenzli/down) or [utop](https://github.com/ocaml-community/utop) are recommended to enhance\nyour REPL experience while working through these examples. They will add autocompletion support\nwhich can be useful when navigating a new library.\n\nWe will start by setting up the toplevel by asking it to load routes.\n\n```ocaml\n# #require \"routes\";;\n```\n\nWe will start by defining a few simple routes that don't need to extract any path parameter.\n\n```ocaml\n# (* A simple route that matches the empty path segments. *);;\n# let root () = Routes.nil;;\nval root : unit -\u003e ('a, 'a) Routes.path = \u003cfun\u003e\n\n# (* We can combine multiple segments using `/` *);;\n# let users () = Routes.(s \"users\" / s \"get\" /? nil);;\nval users : unit -\u003e ('a, 'a) Routes.path = \u003cfun\u003e\n```\n\nWe can use these route definitions to get a string \"pattern\" that can potentially be used\nto show what kind of routes your application can match.\n\n```ocaml\n# Routes.string_of_path (root ());;\n- : string = \"/\"\n\n# Routes.string_of_path (users ());;\n- : string = \"/users/get\"\n```\n\nMatching routes where we don't need to extract any parameter could be done with a simple string match.\nThe part where routers are useful is when there is a need to extract some parameters are extracted from the\npath.\n\n```ocaml\n# let sum () = Routes.(s \"sum\" / int / int /? nil);;\nval sum : unit -\u003e (int -\u003e int -\u003e 'a, 'a) Routes.path = \u003cfun\u003e\n```\n\nLooking at the type for `sum` we can see that our route knows about our two integer path parameters.\nA route can also extract parameters of different types.\n\n```ocaml\n# let get_user () = Routes.(s \"user\" / str / int64 /? nil);;\nval get_user : unit -\u003e (string -\u003e int64 -\u003e 'a, 'a) Routes.path = \u003cfun\u003e\n```\n\nWe can still pretty print such routes to get a human readable \"pattern\" that can be used to inform\nsomeone what kind of routes are defined in an application.\n\nOnce we start working with routes that extract path parameters, there is another operation that can sometimes\nbe useful. Often times there can be a need to generate a URL from a route. It could be for creating\nhyperlinks in HTML pages, creating target URLs that can be forwarded to HTTP clients, etc.\n\nUsing routes we can create url targets from the same type definition that is used for performing a route match.\nUsing this approach for creating url targets has the benefit that whenever a route definition is updated,\nthe printed format for the url target will also reflect that change. If the types remain the same,\nthen the printing functions will automatically start generating url targets that\nreflect the change in the route type, and if the types change the user will get a compile time error about mismatched\ntypes. This can be useful in ensuring that we avoid using bad/outdated URLs in our application.\n\n```ocaml\n# Routes.string_of_path (sum ());;\n- : string = \"/sum/:int/:int\"\n\n# Routes.string_of_path (get_user ());;\n- : string = \"/user/:string/:int64\"\n\n# Routes.sprintf (sum ());;\n- : int -\u003e int -\u003e string = \u003cfun\u003e\n\n# Routes.sprintf (get_user ());;\n- : string -\u003e int64 -\u003e string = \u003cfun\u003e\n\n# Routes.sprintf (sum ()) 45 12;;\n- : string = \"/sum/45/12\"\n\n# Routes.sprintf (sum ()) 11 56;;\n- : string = \"/sum/11/56\"\n\n# Routes.sprintf (get_user ()) \"JohnUser\" 1L;;\n- : string = \"/user/JohnUser/1\"\n\n# Routes.sprintf (get_user ()) \"foobar\" 56121111L;;\n- : string = \"/user/foobar/56121111\"\n```\n\nWe've seen a few examples so far, but none of any actual routing. Before we can perform a route match,\nwe need to connect a route definition to a handler function that gets called when a successful match happens.\n\n```ocaml\n# let sum_route () = Routes.(sum () @--\u003e fun a b -\u003e Printf.sprintf \"%d\" (a + b));;\nval sum_route : unit -\u003e string Routes.route = \u003cfun\u003e\n\n# let user_route () = Routes.(get_user () @--\u003e fun name id -\u003e Printf.sprintf \"(%Ld) %s\" id name);;\nval user_route : unit -\u003e string Routes.route = \u003cfun\u003e\n\n# let root () = Routes.(root () @--\u003e \"Hello World\");;\nval root : unit -\u003e string Routes.route = \u003cfun\u003e\n```\n\nNow that we have a collection of routes connected to handlers, we can create a router and perform route matching.\nSomething to keep in mind is that we can only combine routes that have the same final return type, i.e. handlers\nattached to every route in a router should have the same type for the values they return.\n\n```ocaml\n# let routes = Routes.one_of [sum_route (); user_route (); root ()];;\nval routes : string Routes.router = \u003cabstr\u003e\n\n# Routes.match' routes ~target:\"/\";;\n- : string Routes.match_result = Routes.FullMatch \"Hello World\"\n\n# Routes.match' routes ~target:\"/sum/25/11\";;\n- : string Routes.match_result = Routes.FullMatch \"36\"\n\n# Routes.match' routes ~target:\"/user/John/1251\";;\n- : string Routes.match_result = Routes.FullMatch \"(1251) John\"\n\n# Routes.match' routes ~target:(Routes.sprintf (sum ()) 45 11);;\n- : string Routes.match_result = Routes.FullMatch \"56\"\n\n# (* This route is not an exact match because of the final trailing slash. *);;\n# Routes.match' routes ~target:\"/sum/1/2/\";;\n- : string Routes.match_result = Routes.MatchWithTrailingSlash \"3\"\n```\n\n## Dealing with trailing slashes\n\nEvery route definition can control what behavior it expects when it encounters\na trailing slash. In the examples above all route definitions ended with\n`/? nil`. This will result in an exact match if the route does not end in a trailing slash.\nIf the input target matches every paramter but has an additional trailing slash, the route will\nstill be considered a match, but it will inform the user that the matching route was found,\neffectively having disregarded the trailing slash.\n\n```ocaml\n# let no_trail () = Routes.(s \"foo\" / s \"bar\" / str /? nil @--\u003e fun msg -\u003e String.length msg);;\nval no_trail : unit -\u003e int Routes.route = \u003cfun\u003e\n\n# Routes.(match' (one_of [ no_trail () ]) ~target:\"/foo/bar/hello\");;\n- : int Routes.match_result = Routes.FullMatch 5\n\n# Routes.(match' (one_of [ no_trail () ]) ~target:\"/foo/bar/hello/\");;\n- : int Routes.match_result = Routes.MatchWithTrailingSlash 5\n```\n\nMore example of library usage can be seen in the [examples](https://github.com/anuragsoni/routes/tree/main/example) folder,\nand as part of the [test](https://github.com/anuragsoni/routes/blob/main/test/routing_test.ml) definition.\n\n## Installation\n\n**To use the version published on opam:**\n\n```\nopam install routes\n```\n\n**For development version:**\n\n```\nopam pin add routes git+https://github.com/anuragsoni/routes.git\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanuragsoni%2Froutes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanuragsoni%2Froutes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanuragsoni%2Froutes/lists"}