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
root { "Hello from Hanami" }
end
end
Defining routes
Each route in Hanami's router is comprised of:
- a HTTP method (i.e.
get,post,put,patch,delete,optionsortrace) - 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"]] }
Resource routes
To define a full set of RESTful routes for a resource, use the resources or resource methods.
Use resources (plural) to generate a full set of RESTful routes for a collection resource:
resources :books
This generates the following routes:
GET /books books.index
GET /books/:id books.show
GET /books/new books.new
POST /books books.create
GET /books/:id/edit books.edit
PATCH /books/:id books.update
DELETE /books/:id books.destroy
Use resource (singular) for singleton resources that don't have an index action or ID:
resource :profile
This generates the following routes:
GET /profile profile.show
GET /profile/new profile.new
POST /profile profile.create
GET /profile/edit profile.edit
PATCH /profile profile.update
DELETE /profile profile.destroy
To generate only specific actions, use the only option:
resources :books, only: [:index, :show]
To exclude specific actions, use the except option:
resources :books, except: [:destroy]
Customize the URL path using the path option:
resources :comments, path: "reviews"
# Routes will use /reviews instead of /comments
Override the action namespace using the to option:
resources :books, to: "library.books"
# Routes will invoke actions under library.books namespace
Customize route names using the as option:
resources :books, as: :publications
# Named routes will use :publications (e.g., :publications_path)
Resources can be nested (to any level) to represent hierarchical relationships:
resources :books do
resources :reviews
end
This generates nested routes like /books/:book_id/reviews.
The actions for nested resources are namespaced by any parent resources. In the above example, the reviews index route will have a to: of "books.reviews.index".
Basic routes can also be nested within resources:
resources :books do
get "/latest", to: "books.latest"
end
This generates a route at "/books/latest".
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:
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
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:
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:
scope "about" do
get "/contact-us", to: "content.contact", as: :contact # => /about/contact-us
get "/faq", to: "content.faq", as: :faq # => /about/faq
end
end
end
Scopes supply a prefix to the names of their enclosed routes. The routes above are accessible as path(:about_contact) and path(:about_faq).
Scopes can also include all the elements of regular routes. In this case, you can supply a friendlier name prefix with as::
scope "authors/:author_id", as: :author do
resources :books, only: [:index, :show], to: "authors.books"
get "/news", to: "authors.news", as: :news
end
end
end
This creates the following routes:
GET /authors/:author_id/books authors.books.index :author_books
GET /authors/:author_id/books/:id authors.books.show :author_book
GET /authors/:author_id/news authors.news :author_news
If you need to create a route name with a prefix that precedes a scope prefix, provide an array to as:. The scope prefix will be inserted after the first element.
scope "authors/:author_id", as: :author do
get "send-feedback", to: "authors.send_feedback", as: [:send, :feedback]
# Will be named :send_author_feedback
end
Redirects
Redirects can be added using redirect.
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
Redirects can be made to absolute URLs as well:
redirect "/external", to: "http:/hanamirb.org"
Non-http protocols are also supported thanks to the URI class:
redirect "/custom", to: URI("xmpp://myapp.net")
If you have many redirects or need to implement custom logic, you might consider using a Rack middleware.
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
GET /books/:id/edit books.edit
PATCH /books/:id books.update
DELETE /books/:id books.destroy