ruby 46 lines · 7 steps

Centralized API error handling in Rails

A base controller maps common exceptions to consistent JSON error responses so every endpoint fails the same way.

Explained by highlit
1module Api
2 module V1
3 class BaseController < ActionController::API
4 rescue_from StandardError, with: :render_internal_error
5 rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
6 rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable
7 rescue_from ActionController::ParameterMissing, with: :render_bad_request
8 rescue_from Pundit::NotAuthorizedError, with: :render_forbidden
9 
10 private
11 
12 def render_not_found(error)
13 render_error(status: :not_found, code: "not_found", message: error.message)
14 end
15 
16 def render_unprocessable(error)
17 render_error(
18 status: :unprocessable_entity,
19 code: "validation_failed",
20 message: "Validation failed",
21 details: error.record.errors.map { |e| { field: e.attribute, message: e.message } }
22 )
23 end
24 
25 def render_bad_request(error)
26 render_error(status: :bad_request, code: "bad_request", message: error.message)
27 end
28 
29 def render_forbidden(_error)
30 render_error(status: :forbidden, code: "forbidden", message: "You are not authorized to perform this action")
31 end
32 
33 def render_internal_error(error)
34 Rails.logger.error("[#{request.request_id}] #{error.class}: #{error.message}")
35 Rails.logger.error(error.backtrace.take(20).join("\n"))
36 render_error(status: :internal_server_error, code: "internal_error", message: "Something went wrong")
37 end
38 
39 def render_error(status:, code:, message:, details: [])
40 payload = { error: { code: code, message: message } }
41 payload[:error][:details] = details if details.any?
42 render json: payload, status: status
43 end
44 end
45 end
46end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Registering rescue_from handlers in a base controller gives every subclass uniform error responses for free.
  2. 2Order matters: specific exceptions before the StandardError catch-all so they don't get swallowed.
  3. 3A single render helper keeps the error payload shape consistent across every failure case.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Centralized API error handling in Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code