Need to return JSON-formatted 404 error in Rails

Ruby on-Rails

Ruby on-Rails Problem Overview


I am having a normal HTML frontend and a JSON API in my Rails App. Now, if someone calls /api/not_existent_method.json it returns the default HTML 404 page. Is there any way to change this to something like {"error": "not_found"} while leaving the original 404 page for the HTML frontend intact?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

A friend pointed me towards a elegant solution that does not only handle 404 but also 500 errors. In fact, it handles every error. The key is, that every error generates an exception that propagates upwards through the stack of rack middlewares until it is handled by one of them. If you are interested in learning more, you can watch this excellent screencast. Rails has it own handlers for exceptions, but you can override them by the less documented exceptions_app config option. Now, you can write your own middleware or you can route the error back into rails, like this:

# In your config/application.rb
config.exceptions_app = self.routes

Then you just have to match these routes in your config/routes.rb:

get "/404" => "errors#not_found"
get "/500" => "errors#exception"

And then you just create a controller for handling this.

class ErrorsController < ActionController::Base
  def not_found
    if env["REQUEST_PATH"] =~ /^\/api/
      render :json => {:error => "not-found"}.to_json, :status => 404
    else
      render :text => "404 Not found", :status => 404 # You can render your own template here
    end
  end

  def exception
    if env["REQUEST_PATH"] =~ /^\/api/
      render :json => {:error => "internal-server-error"}.to_json, :status => 500
    else
      render :text => "500 Internal Server Error", :status => 500 # You can render your own template here
    end
  end
end

One last thing to add: In the development environment, rails usally does not render the 404 or 500 pages but prints a backtrace instead. If you want to see your ErrorsController in action in development mode, then disable the backtrace stuff in your config/enviroments/development.rb file.

config.consider_all_requests_local = false

Solution 2 - Ruby on-Rails

I like to create a separate API controller that sets the format (json) and api-specific methods:

class ApiController < ApplicationController
  respond_to :json

  rescue_from ActiveRecord::RecordNotFound, with: :not_found
  # Use Mongoid::Errors::DocumentNotFound with mongoid

  def not_found
    respond_with '{"error": "not_found"}', status: :not_found
  end
end

RSpec test:

  it 'should return 404' do
    get "/api/route/specific/to/your/app/", format: :json
    expect(response.status).to eq(404)
  end

Solution 3 - Ruby on-Rails

Sure, it will look something like this:

class ApplicationController < ActionController::Base
  rescue_from NotFoundException, :with => :not_found
  ...

  def not_found
    respond_to do |format|
      format.html { render :file => File.join(Rails.root, 'public', '404.html') }
      format.json { render :text => '{"error": "not_found"}' }
    end
  end
end

NotFoundException is not the real name of the exception. It will vary with the Rails version and the exact behavior you want. Pretty easy to find with a Google search.

Solution 4 - Ruby on-Rails

Try to put at the end of your routes.rb:

match '*foo', :format => true, :constraints => {:format => :json}, :to => lambda {|env| [404, {}, ['{"error": "not_found"}']] }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioniblueView Question on Stackoverflow
Solution 1 - Ruby on-RailsiblueView Answer on Stackoverflow
Solution 2 - Ruby on-RailsGreg FuntusovView Answer on Stackoverflow
Solution 3 - Ruby on-RailsbioneuralnetView Answer on Stackoverflow
Solution 4 - Ruby on-RailsjdoeView Answer on Stackoverflow