Actions and Views in Hanami

· 5 min read · Updated March 7, 2026 · beginner
hanami ruby web-development views

In our Getting Started with Hanami 2 guide, we set up a new Hanami application and explored the basic project structure. Now it’s time to dive into one of the most important concepts in any web framework: how to handle requests and return responses.

Hanami uses a pattern called actions for handling HTTP requests and views for rendering responses. This separation of concerns keeps your code clean and maintainable. In this guide, you’ll learn how to create actions, work with parameters, render templates, and return JSON responses.

Understanding Actions in Hanami

Actions are the entry point for HTTP requests in Hanami. Each action corresponds to a specific URL and HTTP method combination. When a request comes in, Hanami dispatches it to the appropriate action, which then processes the request and returns a response.

Here’s a simple action in Hanami:

# app/actions/books/index.rb
module Bookshelf
  module Actions
    module Books
      class Index < Bookshelf::Action
        def handle(request, response)
          response[:books] = ['Ruby Programming', 'Rails 7 Guide']
        end
      end
    end
  end
end

The action inherits from Bookshelf::Action, which provides the handle method with two parameters: the request object and the response object. The request contains all the information about the incoming HTTP request, while the response is what you’ll use to build the response.

Routing and Parameters

Hanami uses a DSL for defining routes. Each route maps a URL pattern to an action. Let’s look at how to set up routing:

# config/routes.rb
module Bookshelf
  class Routes
    define do
      root to: 'home#index'
      
      get '/books', to: 'books#index'
      get '/books/:id', to: 'books#show'
      post '/books', to: 'books#create'
      put '/books/:id', to: 'books#update'
      delete '/books/:id', to: 'books#destroy'
    end
  end
end

The :id part of the route is a route parameter. You can access it in your action through the request object:

# app/actions/books/show.rb
module Bookshelf
  module Actions
    module Books
      class Show < Bookshelf::Action
        def handle(request, response)
          book_id = request.params[:id]
          
          book = BookRepository.new.find(book_id)
          
          if book
            response[:book] = book
          else
            response.status = 404
            response[:error] = 'Book not found'
          end
        end
      end
    end
  end
end

The request.params method gives you access to all parameters, including:

  • Route parameters (like :id from the URL)
  • Query string parameters (like ?page=2)
  • POST body parameters (from form submissions)

Query Parameters vs Route Parameters

Hanami combines all types of parameters into a single params object. Here’s how to work with query parameters:

# app/actions/books/index.rb
module Bookshelf
  module Actions
    module Books
      class Index < Bookshelf::Action
        def handle(request, response)
          page = request.params[:page] || 1
          per_page = request.params[:per_page] || 10
          
          books = BookRepository.new.all(paginate: (page.to_i - 1) * per_page.to_i, limit: per_page.to_i)
          
          response[:books] = books
          response[:pagination] = {
            page: page.to_i,
            per_page: per_page.to_i
          }
        end
      end
    end
  end
end

Now you can filter books using URLs like /books?page=2&per_page=25.

Working with Views

In Hanami, actions and views are separate components. The action handles the request logic, and the view handles rendering the response. This separation of concerns keeps your code clean and easier to test.

To render a view from your action, use the render method:

# app/actions/books/index.rb
module Bookshelf
  module Actions
    module Books
      class Index < Bookshelf::Action
        def handle(request, response)
          books = BookRepository.new.all
          
          render response, :index, books: books
        end
      end
    end
  end
end

The corresponding view would look like this:

# app/views/books/index.rb
module Bookshelf
  module Views
    module Books
      class Index < Bookshelf::View
        def template
          html do
            ul do
              books.each do |book|
                li book.title
              end
            end
          end
        end
      end
    end
  end
end

Hanami views use a DSL that lets you build HTML programmatically. This is safer than using inline ERB templates because it’s less prone to XSS attacks.

Returning JSON Responses

Modern web applications often need to return JSON instead of HTML. Hanami makes this straightforward:

# app/actions/books/show.rb
module Bookshelf
  module Actions
    module Books
      class Show < Bookshelf::Action
        def handle(request, response)
          book = BookRepository.new.find(request.params[:id])
          
          if book
            response.format = :json
            response.body = JSON.generate({
              id: book.id,
              title: book.title,
              author: book.author,
              published_year: book.published_year
            })
          else
            response.status = 404
            response.format = :json
            response.body = JSON.generate({ error: 'Book not found' })
          end
        end
      end
    end
  end
end

For better organization, you can also create a dedicated JSON view:

# app/views/books/json_show.rb
module Bookshelf
  module Views
    module Books
      class JsonShow < Bookshelf::View
        format :json
        
        def template
          render json: {
            id: book.id,
            title: book.title,
            author: book.author
          }
        end
      end
    end
  end
end

Handling Form Submissions

Let’s see how to handle a POST request with form data:

# app/actions/books/create.rb
module Bookshelf
  module Actions
    module Books
      class Create < Bookshelf::Action
        def handle(request, response)
          book_params = request.params[:book]
          
          if book_params[:title].nil? || book_params[:title].empty?
            response.status = 422
            response[:errors] = { title: ['is required'] }
            return
          end
          
          book = BookRepository.new.create(book_params)
          
          response.status = 201
          response[:book] = book
        end
      end
    end
  end
end

The form would submit to /books with the parameters nested under the book key:

<form action="/books" method="POST">
  <input type="text" name="book[title]" />
  <input type="text" name="book[author]" />
  <button type="submit">Create Book</button>
</form>

When to Use Actions and Views

Use actions when you need to:

  • Process HTTP requests
  • Validate input parameters
  • Interact with your database or external services
  • Return different response types (HTML, JSON, etc.)

Use views when you need to:

  • Render HTML templates
  • Format data for display
  • Reuse presentation logic across multiple pages

The key principle is that actions should be thin — they handle request routing and coordination, while views handle the presentation logic. This makes your application easier to test and maintain.

Summary

You’ve learned how to create actions and views in Hanami 2. Actions handle incoming HTTP requests and route them to the appropriate logic. Views handle rendering the response, whether it’s HTML or JSON. The key takeaways are:

  1. Actions inherit from Bookshelf::Action and implement a handle method
  2. Use request.params to access route parameters, query strings, and POST data
  3. Views use a DSL for building HTML safely
  4. Set response.format = :json and use response.body for JSON responses
  5. Keep actions thin — delegate presentation logic to views

In the next tutorial, we’ll explore persistence with ROM in Hanami, showing you how to interact with databases using Hanami’s data mapping layer.


Next Tutorial: Persistence with ROM in Hanami