V2.2: Overview


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

module Bookshelf
  class Routes < Hanami::Routes
    root { "Hello from Hanami" }
  end
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.

get "/books", to: "books.index"  # Invokes the Bookshelf::Actions:Books::Index action
post "/books", to: "books.create" # Invokes the Bookshelf::Actions:Books::Create action
get "/rack-app", to: RackApp.new
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.

$ bundle exec hanami generate action books.index
$ bundle exec hanami generate action books.show
$ bundle exec hanami generate action books.new
$ bundle exec hanami generate action books.create
$ bundle exec hanami generate action books.update
$ bundle exec hanami generate action books.destroy
module Bookshelf
  class Routes < Hanami::Routes
    root { "Hello from Hanami" }

    get "/books", to: "books.index"
    get "/books/:id", to: "books.show"
    get "/books/new", to: "books.new"
    post "/books", to: "books.create"
    patch "/books/:id", to: "books.update"
    delete "/books/:id", to: "books.destroy"
  end
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:

module Bookshelf
  class Routes < Hanami::Routes
    root to: "home"
  end
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:

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":

get "/books/:id", to: "books.show"

# GET /books/1
# 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":

get "/books/:book_id/reviews/:id", to: "book_reviews.show"

# GET /books/17/reviews/6
# request.params[:book_id] # => 17
# request.params[:id] # => 6

Accessing these variables in a Hanami action looks like:

# Request: GET /books/17/reviews/6

module Bookshelf
  module Actions
    module BookReviews
      class Show < Bookshelf::Action
        def handle(request, response)
          request.params[:book_id] # 17
          request.params[:id] # 6
        end
      end
    end
  end
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:

get "/books/:id", id: /\d+/, to: "books.show"

# GET /books/2 # matches
# GET /books/two # does not match

get "/books/award-winners/:year", year: /\d{4}/, to: "books.award_winners.index"

# GET /books/award-winners/2022 # matches
# GET /books/award-winners/2 # does not match
# 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":

get "/pages/*path", to: "page_catch_all"

# GET /pages/2022/my-page will invoke the Bookshelf::Actions::PageCatchAll action
# 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:

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

Named routes

Routes can be named using the as option.

get "/books", to: "books.index", as: :books
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.

Hanami.app["routes"].path(:books)
=> "/books"

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

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

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

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

require "hanami"

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

Scopes

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

module Bookshelf
  class Routes < Hanami::Routes
    scope "about" do
      get "/contact-us", to: "content.contact_us" # => /about/contact-us
      get "/faq", to: "content.faq" # => /about/faq
    end
  end
end

Redirects

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

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

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

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:

$ bundle exec hanami routes

GET     /                             home                          as :root
GET     /books                        books.index
GET     /books/:id                    books.show
GET     /books/new                    books.new
POST    /books                        books.create
PATCH   /books/:id                    books.update
DELETE  /books/:id                    books.destroy