Ruby

Handling Exceptions in Grape for Ruby: rescue_from and error!

Handling Exceptions in Grape for Ruby: rescue_from and error!

Grape handles exceptions with rescue_from blocks declared on your API class: rescue_from :all catches every exception, while rescue_from ActiveRecord::RecordNotFound targets one class. Inside an endpoint, error!("Not Found", 404) halts with a formatted error, defaulting to status 500. Without a handler, invalid params return 400 and other exceptions bubble up to Rack. This guide covers Grape 3.3.

Every response body, status code, and error message was verified on Grape 3.3.5, Rack 3.2.7, and Ruby 3.4.10. The examples run standalone — gem install grape rackup puma is the only setup.

How Grape Handles Errors by Default

Out of the box, Grape formats two kinds of failure on its own: errors you report through its error! helper, and the Grape::Exceptions classes it raises itself, such as the validation errors triggered by a missing required param. Any other exception — a database error, a RuntimeError from a typo — leaves Grape unformatted unless you declare a rescue_from handler for it.

A small API is enough to see all of this behavior:

Ruby
# app.rb
require "grape"
 
JOBS = {
  "1" => { id: 1, title: "Backend Engineer" }
}
 
class API < Grape::API
  format :json
 
  resource :jobs do
    get ":id" do
      job = JOBS[params[:id]]
      job || error!({ error: "Not Found" }, 404)
    end
  end
 
  params do
    requires :name, type: String
    requires :age, type: Integer
  end
  post :people do
    { name: params[:name], age: params[:age] }
  end
end
Ruby
# config.ru
require_relative "app"
 
run API

Start the server with rackup, and the API answers on port 9292 (rackup’s default).

Raising Errors with error!

error! stops endpoint execution immediately and renders its first argument through the API’s error formatter, with the status you pass as the second argument:

Shell
curl http://localhost:9292/jobs/1
# => {"id":1,"title":"Backend Engineer"}
 
curl http://localhost:9292/jobs/2
# => {"error":"Not Found"}

The first request responds with status 200; the second, where error! runs, with status 404.

The status argument is optional. Without it, error! responds with status 500:

Ruby
get :broken do
  error!("Something went wrong")
end

GET /broken responds with status 500 and the body {"error":"Something went wrong"} — under format :json, a plain string message is wrapped in an error key.

error! covers failures your own code detects. For failures Ruby raises, the language gives you begin/rescue, covered in depth in Rescuing Exceptions in Ruby:

Ruby
begin
  # code that may raise an exception
rescue => e
  # runs when an exception is raised
else
  # runs when no exception is raised
ensure
  # always runs
end

rescue => e captures the exception into e; leaving the variable off — a bare rescue => — is a SyntaxError. Wrapping every endpoint in a begin block gets repetitive fast, though. Grape’s answer is rescue_from.

Rescuing Exceptions with rescue_from

rescue_from declares an exception handler for the whole API class. Pass an exception class to target one family of failures, or :all to catch everything:

Ruby
class API < Grape::API
  format :json
 
  rescue_from :all do |e|
    error!({ error: e.message }, 500)
  end
 
  get :boom do
    raise "something went wrong"
  end
end

GET /boom now responds with status 500 and the body {"error":"something went wrong"} instead of a raw exception.

The handler block receives the exception and must end in one of three ways: call error!, return a Rack::Response, or raise. Anything else produces the 500 Invalid response error described in the next section. Raising from a handler does not restore Grape’s default behavior either: on Grape 3.3.5, the re-raised exception comes back as a generic 500 with the body {"error":"Internal Server Error"}. That makes error! the ending nearly every handler wants.

A single catch-all flattens every failure into one status code, which misleads API clients: a missing record is not a server error. Declare handlers for specific classes alongside the catch-all, and each failure keeps a meaningful status:

Ruby
class JobNotFoundError < StandardError; end
 
class API < Grape::API
  format :json
 
  rescue_from JobNotFoundError do |e|
    error!({ error: e.message }, 404)
  end
 
  rescue_from :all do |e|
    error!({ error: e.message }, 500)
  end
 
  resource :jobs do
    get ":id" do
      raise JobNotFoundError, "job #{params[:id]} not found" unless JOBS[params[:id]]
 
      JOBS[params[:id]]
    end
  end
end

With both handlers in place, GET /jobs/2 responds with status 404 and {"error":"job 2 not found"}, while any other exception still lands in the catch-all with a 500. In a Rails-backed API, rescue_from ActiveRecord::RecordNotFound fills the same slot.

A third argument to error! adds response headers:

Ruby
rescue_from :all do |e|
  error!({ error: e.message }, 500, { "Cache-Control" => "no-store" })
end

One gotcha deserves its own warning: rescue_from :all also catches Grape::Exceptions::ValidationErrors. With the catch-all handler in place, a request that fails validation no longer gets Grape’s default 400 — POST an empty body to the /people endpoint from the first example, and the response is status 500 with the body {"error":"name is missing, age is missing"}. A client mistake now reads as a server error, and re-raising is no way out. Declare a handler for validation errors instead:

Ruby
rescue_from Grape::Exceptions::ValidationErrors do |e|
  error!({ errors: e.full_messages }, 400)
end

Validation failures now respond with status 400 and the body {"errors":["name is missing","age is missing"]}full_messages returns each failure as its own element, a friendlier shape for clients than the joined default message.

Grape Error Messages and What They Mean

The errors in this section are the ones Grape produces itself, reproduced on Grape 3.3.5. Each one points at a specific mistake.

name is missing

A required parameter was not sent. Grape raises Grape::Exceptions::ValidationErrors and responds with status 400 before your endpoint code runs; under format :json, the body is {"error":"name is missing"}. Several failures join into one message — {"error":"name is missing, age is missing"} — and a parameter that arrives but fails type coercion produces the same shape with a different verb: sending age=abc to a requires :age, type: Integer param responds with {"error":"age is invalid"}. Without format :json, the same failure is a text/plain response whose entire body is name is missing.

The fix depends on which side of the API you are on: as a client, send the parameter the params block requires; as the API author, rescue Grape::Exceptions::ValidationErrors — as in the full_messages handler in the previous section — to reshape the response without losing the 400.

Invalid response

A rescue_from handler ended with a plain value — a hash, a string, nil — instead of calling error!, returning a Rack::Response, or raising:

Ruby
rescue_from :all do |e|
  { error: e.message } # responds with status 500, {"error":"Invalid response"}
end

Grape discards the returned value and responds with status 500 and the body {"error":"Invalid response"}, hiding the original exception entirely. The fix is to end the handler with error!({ error: e.message }, 500).

Exceptions That Bypass Grape

The last failure mode has no error string, and that absence is the symptom: without a rescue_from handler, an exception Grape does not own gets no formatted response at all. Raise a RuntimeError in an endpoint of a handler-less API and the exception propagates out of Grape to the Rack server. In development, rackup responds with Rack::ShowExceptions’ HTML debug page and prints the backtrace to the terminal; in production mode, the same request gets the server’s bare 500 with an empty body. Neither response is JSON, so your API’s contract breaks at the same moment as its code. The fix is a rescue_from :all backstop that turns unexpected exceptions into well-formed 500s — paired with error monitoring, so the exceptions it hides from clients stay visible to you.

Custom Error Classes in Grape

Handlers stay manageable when your application’s failures share a hierarchy. The pattern from Diving into Custom Exceptions in Ruby — a base class that owns shared behavior, subclasses that fill in specifics — maps directly onto Grape: give the base class a status, and one handler serves the whole family.

Ruby
class BaseError < StandardError
  attr_reader :status
 
  def initialize(message = "Something unexpected happened", status = 500)
    @status = status
    super(message)
  end
end
 
class NotFoundError < BaseError
  def initialize(message = "The record you are looking for does not exist")
    super(message, 404)
  end
end
 
class API < Grape::API
  format :json
 
  rescue_from BaseError do |e|
    error!({ error: e.message }, e.status)
  end
 
  rescue_from :all do |e|
    error!({ error: e.message }, 500)
  end
 
  resource :jobs do
    get ":id" do
      raise NotFoundError unless JOBS[params[:id]]
 
      JOBS[params[:id]]
    end
  end
end

rescue_from BaseError matches subclasses too, so GET /jobs/2 responds with status 404 and {"error":"The record you are looking for does not exist"}: the status travels with the exception class, and adding a new error type means adding a subclass, not a handler.

A handler can return a Rack::Response instead of calling error!:

Ruby
rescue_from :all do |e|
  Rack::Response.new({ error: e.message }.to_json, 500)
end

This still works on Grape 3.3.5, with a catch: the response skips Grape’s formatter and ships without a Content-Type header. Prefer error! inside handlers — it keeps the formatter and rules out both the missing header and the Invalid response failure mode.

Grape has two more settings worth knowing. default_error_status changes the status a status-less error! responds with:

Ruby
class API < Grape::API
  format :json
  default_error_status 422
 
  get :broken do
    error!("Something went wrong")
  end
end

GET /broken now responds with status 422 instead of 500. And rescue_from accepts a helper method in place of a block, which keeps a long list of handlers readable:

Ruby
class API < Grape::API
  format :json
 
  helpers do
    def handle_argument_error(e)
      error!({ error: e.message }, 400)
    end
  end
 
  rescue_from ArgumentError, with: :handle_argument_error
 
  get :convert do
    { value: Integer(params[:value].to_s) }
  end
end

GET /convert?value=abc responds with status 400 and the body {"error":"invalid value for Integer(): \"abc\""}.

Best Practices for Grape Exception Handling

  1. Rescue specific exception classes alongside rescue_from :all, so each failure keeps a meaningful status code and the catch-all stays a backstop.
  2. Declare a handler for Grape::Exceptions::ValidationErrors whenever you use rescue_from :all, so validation failures stay 400s instead of turning into 500s.
  3. End every handler with error! — it keeps the error formatter, the Content-Type header, and the original message, and it rules out the Invalid response 500.
  4. Group related failures under a base class that carries its own status, and rescue the base class once.

Monitoring Grape Exceptions with AppSignal

A rescue_from handler that returns a tidy JSON body also keeps the exception out of sight — in production, that silence is the problem. AppSignal’s Grape integration reports the exceptions your API raises, grouped by endpoint, so a failing route surfaces in your dashboard before an API consumer reports it.

AppSignal dashboard showing exceptions from a Grape API grouped by endpoint

Wrapping Up

Grape’s exception toolkit is small and predictable: error! for failures your code detects, rescue_from for everything raised, custom error classes to keep the two organized, and automatic 400s for validation failures. The sharp edges are the quiet ones — a catch-all that swallows validation errors, a handler that returns the wrong type, an unrescued exception that bypasses Grape entirely — and each has a short fix you can verify with curl.

Happy coding!

P.S. If you’d like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!

Frequently asked questions

How do I rescue all exceptions in a Grape API?
Declare rescue_from :all with a block on your API class; the block receives the exception and must call error!, return a Rack response, or re-raise. Rescue specific classes first so they get correct status codes.
What does name is missing mean in Grape?
It is Grape’s validation error for a required parameter that was not sent. Grape raises Grape::Exceptions::ValidationErrors and responds with status 400 before your endpoint code runs.
What status code does Grape error! return by default?
Without an explicit status, error! responds with 500. You can pass a status as the second argument, or change the default for an API with default_error_status.
Does rescue_from :all catch validation errors in Grape?
Yes. A rescue_from :all handler also receives Grape::Exceptions::ValidationErrors, so a handler that always returns 500 turns 400 validation errors into server errors. Rescue validation errors separately first.

Published , Updated

Wondering what you can do next?

  • Share this article on social media
Kingsley Chijioke

Kingsley Chijioke

Our guest author Kingsley is a Software Engineer who enjoys writing technical articles.

All articles by Kingsley Chijioke

Become our next author!

Find out more
$appsignal install

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal