Examples of best practices for handling bad requests in Phoenix?

I’m wondering if anyone has any educational (i.e. simplified) examples that demonstrate best practices when it comes to handling bad requests in a Phoenix application. For educational purposes, let’s consider just one case:

Creating a record: how to handle a bad payload
Maybe your router has something like this:

  scope "/api", MyAppWeb do
    resources("/things", ThingController)
  end

And maybe your controller has something like this:

  def create(conn, %{"data" => data}) do
    with {:ok, thing} <- ThingCreate.create(data) do
      conn
      |> put_status(:created)
      |> render("thing.json", thing: thing)
    end
  end

You’ll notice that the route won’t pattern-match if your payload is something like [] (an empty JSON array)…

It’s tempting to write another controller function that would handle the bad data:

  def create(conn, _bad_data) do
    # return an error...

but that’s just smelly

This article looked promising, but it didn’t get into details: https://medium.com/appunite-edu-collection/handling-failures-in-elixir-and-phoenix-12b70c51314b

I think that the correct response for this situation would be a 400 (e.g. 422), but not simply a 404. What is the cleanest way to return an error message with specifics and a proper status code? Is this best handled in the fallback controller? Does anyone have a complete example of this?

Thanks for sharing!

I don’t see that, you don’t have any additional assertions in the function’s head save for the map requirement with a singular "data" key – nor do you have when clauses. Why wouldn’t this:

def create(conn, %{"data" => data}) do

…match %{"data" => []}? Or you meant if the object itself is not a map at all and just []?

Assuming it’s the latter, then:

…is not a factual statement. In our team we use pattern-matching function heads all the time and they really do help readability and expressing your intent much better than huge case or cond arms in the code of a single controller action function.

I meant the latter: only an empty array being posted, not part of a data payload.

Of course, code smells are a matter of opinion.

So is your solution to add multiple controller functions to pattern-match on bad inputs? In this case you could get away with just one extra function… but then I start thinking about other edge-cases and I worry about having to keep adding other functions. Do you have an example you can share?

Example no, but I believe it’s quite okay for your web server to error out with HTTP 500 when the input is totally unexpected. Your web app is not Skynet.

Don’t overthink error handling. Pattern match on your expected data shape, return errors in a couple of valid cases where people might make small mistakes, and leave Phoenix/Cowboy to error out on everything else.

Hmm… I don’t think I agree with that. My understanding of HTTP response codes are that a 500 error response indicates that the problem is on the server and (importantly) that the client could retry the request. In the case of badly formatted data, that should be some type of 400, indicating that the request is bad and should not be repeated.

Sure, you caught me late at night not thinking entirely straight. Be proud! :stuck_out_tongue: