
# Roundabout
A route generator for Gleam.
[](https://hex.pm/packages/roundabout)
[](https://hexdocs.pm/roundabout/)
## Why
Gleam gives us a great way of matching paths via pattern matching:
```gleam
pub type Route {
Users
User(id: String)
NotFound
}
pub fn get_route(segments: List(String)) {
case segments {
["users"] -> Users
["users", id] -> User(id)
_ -> NotFound
}
}
```
However, this doesn't provide a type safe way of constructing a path from type e.g.
```
User("12") -> "/users/12"
```
See <https://www.kurz.net/posts/gleam-routing> for a more detailed explanation.
This packages provides a generator which gives you:
- A function for converting paths to Route types (`segments_to_route`)
- A function for converting Route types to paths (`route_to_path`)
- Helpers for generating route types and paths for each route
This generator can be used in frontend or backend applications, at it only generates the route types and helper functions. You still need to write your own router using these types.
## Install
```sh
gleam add roundabout@1
```
## Generating routes
Create a module in your project which defines the route definitions and calls the generator. e.g. in `dev/generate_routes.gleam`
```gleam
import roundabout.{fixed, int, route, str}
pub fn main() -> Nil {
let routes = [
route("home", path: [], children: []),
// Will match an individual order e.g. /orders/123
route("order", path: [fixed("orders"), int("id")], children: []),
route(
"user",
path: [fixed("users"), int("id")],
children: [
// Will match /users/123
route("show", path: [], children: []),
// Will match /users/123/delete
route("delete", path: [fixed("delete")], children: []),
],
),
]
roundabout.main(routes, "src/my_app/generated/routes")
}
```
Call this using:
```sh
gleam run -m generate_routes
```
See example output at `examples/src/example_app/generated/routes.gleam`
## Using this in your application
After the routes have been generated, you can use them in your router or views like:
```gleam
import generated/routes
pub fn handle(segments: List(String)) {
let maybe_route = routes.segments_to_route(segments)
case maybe_route {
Ok(routes.Home) -> handle_home()
Ok(routes.Order(id)) -> handle_order(id)
...
Error(_) -> handle_not_found()
}
}
```
## Notes
### The order is important
If you have routes like:
```gleam
[
Route(name: "show", path: [Str("id")], children: []),
Route(name: "invite", path: [Lit("invite")], children: []),
]
```
The first one will always match over the second one, make sure that literal routes are first.
## Structure your routes to support your middleware
If you want to use different middlewares at different levels of your application, you can structure your routes to support this.
For example, having:
```gleam
const routes = [
Route(name: "home", path: [], children: []),
Route(
name: "app",
path: [Lit("app")],
children: [
// Will match /app/
Route(name: "dashboard", path: [], children: []),
],
),
]
```
Allows to apply some middleware for authentication like:
```gleam
import generated/routes
import middleware
import wisp
pub fn handle(req: Request,, ctx: Context) {
let segments = wisp.path_segments(req)
let maybe_route = routes.segments_to_route(segments)
case maybe_route {
Ok(routes.Home) -> handle_home()
Ok(routes.App(sub)) -> {
use authenticated_context <- middleware.require_session(req, ctx)
case sub {
routes.Dashboard -> handle_dashboard(authenticated_context)
}
}
...
Error(_) -> handle_not_found()
}
}
```
---
Further documentation can be found at <https://hexdocs.pm/roundabout>.