Lucky Logo

# Handling Requests

When a request for a path calls an action, the action has access to the request object through a request method.

The request object is an instance of HTTP::Request.

Lucky also provides access to some helpful methods to determine the requests desired response format.

  • json? - true if the client accepts “application/json”
  • ajax? - true if the X-Requested-With header is “XMLHttpRequest”
  • html? - true if the client accepts HTML
  • xml? - true if the client accepts is “application/xml” or “application/xhtml+xml”
  • plain? - true if the client accepts is “text/plain”
  • multipart? - true if the client accepts starts with “multipart/form-data”

You can use these methods to direct the request or return different responses.

class Users::Show < BrowserAction
  get "/me" do
    if json?
      # The client wants json, so let's return some json
      # Just render the page like normal
      html Users::ShowPage, user: current_user

# Setting accepted request formats

By default, generated Lucky apps set some helpful boundaries around what formats each action in your application can support. These can be overriden in specific actions with the accepted_formats method.

For example, API actions only support JSON requests. Because only one format is specified as accepted, it is automatically set as the default format:

abstract class ApiAction < Lucky::Action
  accepted_formats [:json]

In an API action, requests would be handled like this:

URL Accept Header Server Response No specific request format JSON (the default format) Accept: application/json JSON (the requested, accepted format) Accept: application/csv Response status 406 (not acceptable)

Browser actions, on the other hand, can support either JSON or HTML, and treat non-specified formats as HTML by default:

abstract class BrowserAction < Lucky::Action
  accepted_formats [:html, :json], default: :html

In a browser action, requests would be handled like this:

  • with no specific request format => Server responds with HTML (the default format)
  • with Accept: application/json => Server responds with JSON (the requested, accepted format)
  • with Accept: application/csv => Server responds with 406 (not acceptable)

It’s important to note that this only controls which requests are accepted for processing, and does not automatically create or handle those responses appropriately. For example, requesting with an Accept: application/xml header does not mean that valid XML content will be returned automatically, only that Lucky will allow your action to process the request.

# HTTP Headers

# Accessing Headers

If you need to access or set the headers, you can use request.headers or response.headers.

class Dashboard::Index < BrowserAction
  get "/dashboard" do
    remote_ip = request.headers["X-Forwarded-For"]?

    if remote_ip
      plain_text "The remote IP is #{remote_ip}"
      plain_text "No remote IP found"

# Setting Response Headers

For things like handling CORS, and many other operations like caching, it may be necessary to set response headers. Set these values through the response.headers object.

class Admin::Reports::Show < BrowserAction
  get "/admin/reports/:report_id" do
    response.headers["Cache-Control"] = "max-age=150"
    html ShowPage

You can read more about working with headers in the Crystal docs on HTTP::Headers.

# Handling Responses

Every action is required to return a response

These are the built-in Lucky response methods:

  • html - render a Lucky::HTMLPage
  • redirect - redirect the request to another location
  • plain_text - respond with plain text with text/plain Content-Type.
  • json - return a JSON response with application/json Content-Type.
  • raw_json - similar to json. See Rendering JSON for comparisons.
  • xml - return an XML response with text/xml Content-Type.
  • head - return HTTP HEAD response
  • file - return a file for download
  • data - return a String of data
  • component - render a Component.
class Jobs::Reports::Create < ApiAction
  post "/jobs/reports/" do
    # Run some fancy background job
    if plain?
      # plain text request, return some plain text
      plain_text "Job sent for processing"
      # Respond with HEAD 201
      head 201

The response object is an instance of HTTP::Server::Response.

# HTML with non-200 status

The html() macro will render your Page object with a 200 status. Error status codes like 404, 422, and 500, are handled separately by your Errors::Show action. Read the Error Handling guide for more info.

In cases where you want to render an HTML page, but with a custom status, Lucky provides a separate html_with_status macro.

get "/teaparty" do
  html_with_status IndexPage, 418

# Rendering a file

The file method can be used to return a file and it’s contents to the browser, or render the contents of the file inline to a web browser.

class Reports::Show < BrowserAction

  get "/reports/sales" do
    file "/path/to/reports.pdf",
        disposition: "attachment",
        filename: "report-#{Time.utc.month}.pdf",
        content_type: "application/pdf"

# Rendering raw data

If you need to return a file that already exists, you can use file, but in the case that you only have the String data, you can use this data method.

class Reports::Show < BrowserAction

  get "/reports/sales" do
    report_data = "Street,City,State
123 street, Luckyville, CR
    data report_data,
        disposition: "attachment",
        filename: "report-#{Time.utc.month}.pdf",
        content_type: "application/csv"

The default Content-Type for data is "application/octet-stream"

# Rendering components

The component method allows you to return the HTML generated from a specific component. This can be used in place of rendering an entire HTML page which can be useful for loading dynamic HTML on your front-end.

# src/components/
class CommentComponent < BaseComponent
  needs comment : Comment

  def render

# src/actions/api/comments/
class Api::Comments::Show < ApiAction
  get "/comments/:id" do
    comment =

    component CommentComponent, comment: comment

In this example, you could make a javascript call to this /comments/123 endpoint which would return the HTML just for that comment. This allows you to build out the HTML using Lucky’s type-safe builder, but also injecting dynamic HTML in to your page.

# Custom responses

Lucky provides many different built in responses for automatically setting the appropriate Content-Type header for you. When you need to respond with a non-standard Content-Type, you can use the send_text_response method directly.

class Playlists::Index < BrowserAction

  get "/playlist.m3u8" do
    playlist = "..."
    send_text_response(playlist, "application/x-mpegURL", status: 200)

Text responses are compressed automatically based on two Lucky::Server.settings entries. If alternate behavior is desired, these settings can be adjusted in your Lucky app in config/

  • Lucky::Server.settings.gzip_enabled (enabled in production by default, disabled in development and test. Can be changed in config/
  • Lucky::Server.settings.gzip_content_types

# Redirecting

You can redirect using the redirect method:

Note that for most methods that link you elsewhere (like redirect, or the link helper in HTML pages), you can pass the action directly if it does not need any params. You can see this in the first redirect example below.

class Users::Create < BrowserAction
  post "/users" do
    redirect to: Users::Index # Default status is 302
    redirect to: Users::Show.with(user_id: "user_id") # If the action needs params
    redirect to: "/somewhere_else" # Redirect using a string path
    redirect to: Users::Index, status: 301 # Override status

# Redirect statuses

The default status for a redirect is HTTP::Status::FOUND (302), but if you need a different status code, you can pass any HTTP Status Enum.

# Redirect back

redirect_back allows an action to send the user back to where they made the request from. This is really useful in situations like submitting a lead form where the app might have it in several locations but the app doesn’t need to take them anywhere in particular after they submit it. Rather than sending the user to a specific place after submitting the form, we can now send them back to where they originally submitted it.

class NewsletterSignupsCreate < BrowserAction
  post "/newsletter/signup" do
    # do the signup code
    redirect_back fallback: Home::Index

The fallback argument is required and is used if the HTTP Referer is empty.

For security, Lucky prevents the redirect_back from sending the user back to an external host. If you want to allow this, you’ll need to set the allow_external: true option.

post "/newsletter/signup" do
  # Referer set to
  redirect_back fallback: Home::Index, allow_external: true
See a problem? Have an idea for improvement? Edit this page on GitHub