Routing

Hanami provides a fast, simple router for handling HTTP requests.

Your application’s routes are defined within the Routes class in config/routes.rb

  1. module Bookshelf
  2. class Routes < Hanami::Routes
  3. root { "Hello from Hanami" }
  4. end
  5. end

Adding a route

Each route in Hanami’s router is comprised of:

  • a HTTP method (i.e. get, post, put, patch, delete, options or trace)
  • a path
  • an endpoint to be invoked.

Endpoints are usually actions within your application, but they can also be a block, a Rack application, or anything that responds to #call.

  1. get "/books", to: "books.index" # Invokes the Bookshelf::Actions:Books::Index action
  2. post "/books", to: "books.create" # Invokes the Bookshelf::Actions:Books::Create action
  3. get "/rack-app", to: RackApp.new
  4. get "/my-lambda", to: ->(env) { [200, {}, ["A Rack compatible response"]] }

To add a full set of routes for viewing and managing books, you can either manually add the required routes to your config/routes.rb file, or use Hanami’s action generator, which will generate actions in addition to adding routes for you.

  1. bundle exec hanami generate action books.index
  2. bundle exec hanami generate action books.show
  3. bundle exec hanami generate action books.new
  4. bundle exec hanami generate action books.create
  5. bundle exec hanami generate action books.update
  6. bundle exec hanami generate action books.destroy
  1. module Bookshelf
  2. class Routes < Hanami::Routes
  3. root { "Hello from Hanami" }
  4. get "/books", to: "books.index"
  5. get "/books/:id", to: "books.show"
  6. get "/books/new", to: "books.new"
  7. post "/books", to: "books.create"
  8. patch "/books/:id", to: "books.update"
  9. delete "/books/:id", to: "books.destroy"
  10. end
  11. end

Root request routing

A root method allows you to define a root route for handling GET requests to "/". In a newly generated application, the root path calls a block which returns “Hello from Hanami”. You can instead choose to invoke an action by specifying root to: "my_action". For example, with the following configuration, the router will invoke the home action:

  1. module Bookshelf
  2. class Routes < Hanami::Routes
  3. root to: "home"
  4. end
  5. end

Path matching

The path component of a route supports matching on fixed paths, as well as matching with dynamic path variables.

These variables can be accessed in Hanami actions via request.params[:name], where :name matches the segment’s name specified in the path.

Fixed paths

The following fixed path route matches GET requests for "/books" exactly:

  1. get "/books", to: "books.index"

Paths with variables

Path variables can be used for serving dynamic content. Path variables are defined with a colon followed by a name (for example :id or :slug). These variables can be accessed in Hanami actions via request.params[:name].

The path "/books/:id" matches requests like "/books/1":

  1. get "/books/:id", to: "books.show"
  2. # GET /books/1
  3. # request.params[:id] # => 1

Paths support multiple dynamic variables. For example, the path "/books/:book_id/reviews/:id" matches requests like "/books/17/reviews/6":

  1. get "/books/:book_id/reviews/:id", to: "book_reviews.show"
  2. # GET /books/17/reviews/6
  3. # request.params[:book_id] # => 17
  4. # request.params[:id] # => 6

Accessing these variables in a Hanami action looks like:

  1. # Request: GET /books/17/reviews/6
  2. module Bookshelf
  3. module Actions
  4. module BookReviews
  5. class Show < Bookshelf::Action
  6. def handle(request, response)
  7. request.params[:book_id] # 17
  8. request.params[:id] # 6
  9. end
  10. end
  11. end
  12. end
  13. end

Constraints

Constraints can be added when matching variables. These are regular expressions that must match in order for the route to match. They can be useful for ensuring that ids are digits:

  1. get "/books/:id", id: /\d+/, to: "books.show"
  2. # GET /books/2 # matches
  3. # GET /books/two # does not match
  4. get "/books/award-winners/:year", year: /\d{4}/, to: "books.award_winners.index"
  5. # GET /books/award-winners/2022 # matches
  6. # GET /books/award-winners/2 # does not match
  7. # GET /books/award-winners/two-thousand # does not match

Globbing and catch all routes

Catch all routes can be added using globbing. These routes can be used to handle requests that do not match any preceeding routes.

For example, in the absence of an earlier matching route, "/pages/*match" will match requests for paths like "/pages/2022/my-page":

  1. get "/pages/*path", to: "page_catch_all"
  2. # GET /pages/2022/my-page will invoke the Bookshelf::Actions::PageCatchAll action
  3. # request.params[:path] # # => 2022/my-page

To create a catch all to handle all unmatched GET requests using a custom "unmatched" action, configure this route last:

  1. get "/*path", to: "unmatched"

Named routes

Routes can be named using the as option.

  1. get "/books", to: "books.index", as: :books
  2. get "/books/:id", to: "books.show", as: :book

This enables path and url helpers, which can be accessed via the routes helper registered under "routes" within your application.

  1. Hanami.app["routes"].path(:books)
  2. => "/books"
  3. Hanami.app["routes"].url(:books)
  4. => #<URI::HTTP http://0.0.0.0:2300/books>

When a route requires variables, they can be passed to the helper:

  1. Hanami.app["routes"].path(:book, id: 1)
  2. => "/books/1"

To set a base URL for the url helper, configure it in config/app.rb:

  1. require "hanami"
  2. module Bookshelf
  3. class App < Hanami::App
  4. config.base_url = "https://bookshelf.example.com"
  5. end
  6. end
  1. Hanami.app["routes"].url(:book, id: 1)
  2. => #<URI::HTTP https://bookshelf.example.com/books/1>

Scopes

To nest a series of routes under a particular namespace, you can use a scope:

  1. module Bookshelf
  2. class Routes < Hanami::Routes
  3. scope "about" do
  4. get "/contact-us", to: "content.contact_us" # => /about/contact-us
  5. get "/faq", to: "content.faq" # => /about/faq
  6. end
  7. end
  8. end

Redirects

Redirects can be added using redirect. If you have many redirects, you might consider using a Rack middleware.

  1. redirect "/old", to: "/new"

By default, redirects use a 301 status code. Use a different code via the code option:

  1. redirect "/old", to: "/temporary-new", code: 302

Inspecting routes

Hanami provides a hanami routes command to inspect your application’s routes. Run bundle exec hanami routes on the command line to view current routes:

  1. GET / home as :root
  2. GET /books books.index
  3. GET /books/:id books.show
  4. GET /books/new books.new
  5. POST /books books.create
  6. PATCH /books/:id books.update
  7. DELETE /books/:id books.destroy