dimitarvp

dimitarvp

I’m looking into returning JSON errors from an API app. I know the specs dictate you must always return HTML on certain error codes but I want to circumvent that because I’ll have complete control both on the calling side and the service itself. Plus the service will send logs and telemetry and I want errors to be machine-parseable.

I tried a few things after searching the forum but I couldn’t manage to make any of them work.

Then I stumbled upon Plug.ErrorHandler — Plug v1.20.2. Call me stupid but I have no clue how to use it?

So as a start I want all 404s and 500s to be wrapped with a JSON key errors in an object, returned alongside a 404 and 500 status.

Is this possible using Plug.ErrorHandler? If so, how exactly?

Showing Posts 1 to 7

Exadra37

Exadra37

Please bear in mind that all what I am about to suggest is from the top of my mind, therefore not tested, and may be wrong, but hey it doesn’t hurt for you to give it a try :slight_smile:

  def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
    send_resp(conn, conn.status, "Something went wrong")
  end

If I am not mistake the above will override the call to the Error view at lib/yourapp_web/views/error_view.ex.

I think that this will return it in json:

# You may need to use the Jason library if returning only the map doesn't work.
# You also want to put the `application/json` header in the `conn`
send_resp(conn, conn.status,%{error: conn.status, message:  "Something went wrong"})

Maybe you can simplify the above to use in your router:

# file: lib/yourapp_web/router.ex
  pipeline :api do
    plug :accepts, ["json"],
    plug :handle_errors
  end

  # you may need to pattern match in `conn.status` to make this work
  def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
    # You may need to use the Jason library if returning only the map doesn't work.
    # You also want to put the `application/json` header in the `conn`
    send_resp(conn, conn.status,%{error: conn.status, message:  "Something went wrong"})
  end

EDIT: On a second though I think this isn’t reflecting the intended use from the Plug.ErrorHandler

An Alternative that you can try is to change the Error view to always return a JSON object:

defmodule YourAppWeb.ErrorView do
  use YourAppWeb, :view

  require Logger

  # If you want to customize a particular status code
  # for a certain format, you may uncomment below.
  def render(template, assigns) do
    _json_error(template, assigns)
  end

  # By default, Phoenix returns the status message from
  # the template name. For example, "404.html" becomes
  # "Not Found".
  def template_not_found(template, assigns) do
    _json_error(template, assigns)
  end

  defp _json_error(template, assigns) do

    Logger.info("Error template: #{template}")
    # render("500.html", assigns)
    
    # I am not sure, but I think you can just return a Map, otherwise just try to use the Jason library.
    %{
      error: 400, # You need to extract it from assigns or template name
      maessage: "Whoops!"
    }

  end
  
end
dimitarvp

dimitarvp OP

I still don’t understand where to put this copy-pasted module code in my Phoenix project, and how do I register it to be used – in the router, the endpoint, somewhere else?

The docs kind of assume certain knowledge that I found I don’t have.

Exadra37

Exadra37

I update my previous post while you made this one to make it clear, but here it his again:

# file: lib/yourapp_web/router.ex
  pipeline :api do
    plug :accepts, ["json"],
    plug :handle_errors
  end

  # you may need to pattern match in `conn.status` to make this work
  def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
    # You may need to use the Jason library if returning only the map doesn't work.
    # You also want to put the `application/json` header in the `conn`
    send_resp(conn, conn.status,%{error: conn.status, message:  "Something went wrong"})
  end

This is a guess from me, not actually tried it.

EDIT: On a second though I think this isn’t reflecting the intended use from the Plug.ErrorHandler

dimitarvp

dimitarvp OP

Nope, that didn’t work. :expressionless:

Hitting a non-existent endpoint with curl:

HTTP/1.1 404 Not Found
cache-control: max-age=0, private, must-revalidate
content-length: 1333
content-type: text/markdown; charset=utf-8
date: Mon, 12 Apr 2021 16:33:02 GMT
server: Cowboy

# Phoenix.Router.NoRouteError at POST /does_not_exist

Exception:

    ** (Phoenix.Router.NoRouteError) no route found for POST /does_not_exist (MyAppWeb.Router)
# <snip>

And it’s returning markdown text? Why?

I am still trying to understand how do I just intercept ALL errors and wrap them in keys in a resulting JSON object.

Exadra37

Exadra37

Sorry for not having given you a good solution :frowning:

But did you try the Error view approach? I think that one should work :thinking:

Because you are in development mode. Go to config/dev.exs and set debug_errors to false.

dimitarvp

dimitarvp OP

No idea what to do exactly there either. :003:

It’s been an absolutely awful day and I’d love complete code examples. At least today. If not, I’ll try formulating a solution on a fresh head tomorrow. :smiley:

Exadra37

Exadra37

I showed it to you in my first post:

This file can be found in your app web views folder.

Basically ti’s overriding the render/2 function called to render the error templates for each error status code that is defined in the templates folder.

It also customizes the template_not_found/2 to return the Json error.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
georgeguimaraes
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews