A Phoenix and Elixir REST API— Part 2

Adding POST and PUT

billperegoy
im-becoming-functional
5 min readFeb 19, 2017

--

Where We’re Headed

When we left off last time, we had the beginnings of an API. We now have our database configured, and have the routes and controller actions in place to do GET requests. This time around, we will do some cleanup and add the ability to POST new users and use PUT requests to modify entries. Let’s get right to it!

Setting HTTP Return Status

Currently our GET requests work fine if we do successful calls, but if you try to do make a request on a non-existent entity, you still get a status of 200. We’d like to modify the application to return a 404 in the case on a non-existent record. This is actually very easy. When we perform any operation in the controller, we are just creating a pipeline that modifies the conn structure and returns a modified version of that conn. Phoenix provides a function that adds the proper status code and returns a new conn with that status. Given this, I created a function that accepts a conn and the data Ecto returned, and returns a new conn.

My assumption here is that a database call that returns nil indicates a not found error. So I use pattern matching to return either the :ok atom (200) or the :not_found atom (404).

With this function in place, I then modify the two controller actions to add this transformation. Given that currently this function will only be used within this single controller, I made it a private function using the defp keyword.

With this in place, we now get a 404 returned if we do a get to a non-existent user (i.e. /api/v1/users/100).

Building the POST Endpoint — Route and Controller

If we attempt to perform a POST operation to the users endpoint, we predictably get this error.

We will go down the usual path and add a route.

This gives us another predictable error.

This prods us to open up user_controller.ex and add a create function. For now I’m just putting in a dummy function.

This allows the POST request to run, but nothing is written to the database yet. In order to do this, we need to create an Ecto changeset. The changeset is a function contained within the model that processes casting, validation and constraints for a model. Here is a simple example of changeset for our user model.

Here we simply use the cast function to define the four required fields. We also apply a constraint that will guarantee the the email address is unique. We are doing no other input validation at this time, but it’s quite easy to add constraints similar to those on a Rails model.

Once we have the changeset, we can revisit our model and add code to commit data to our database.

The logic here is quite simple. We provide the params from the API call to the changeset function we just created. That changeset is then sent to the Ecto insert function. The insert function returns a tuple that indicates success or failure. We use pattern matching to determine what HTTP status and data to render into JSON. In the fail case, an Elixir map is returned with a list of error strings.

If you now use a REST client to perform put operations, you will see that valid data is properly written. You can use the get endpoint to conform the proper writing of data.

Adding a PUT Endpoint

Our final major task will be to allow the update of existing records using a put endpoint. For this simple example, we will assume that all fields are provided and we will overwrite them all.

We start off by adding a route.

If we attempt to perform a user put, we get this error.

This spurs us to add a controller action to perform the update.

This was my first attempt. The put request received an additional parameter which contains the id of the record being updated. We use pattern matching to extract that id value into a variable of the same name. We then use an Ecto get to look for a current record with that id value. If that use exists, we use the update changeset with Ecto update to perform the update. Otherwise we return an invalid user error.

So we now have basic get, post and put endpoints that work. We’ll now work to do some cleanup the code and return more useful error messages.

Code Cleanup

Now that we have a working example, let’s clean up the code a bit. The nested conditional in the update function is quite ugly, so I will factor out the “happy path” into a private function.

This allows us to simplify the update function like this.

This feels a lot cleaner to me. I’m still not happy that I don’t have a consistent way of adding the status to the conn map, but I’m not going to improve that at this point.

Conclusion

We now have a very simple REST API to create, read and update this simple user object. I’ve purposely not performed any validation (other than uniqueness) on the model as I intend to use this in conjunction with an Elm front-end that performs validation. Eventually I will add more validation and return that validation back to the Elm front end.

I found that creating the create (POST) and update (PUT) endpoints was relatively straightforward. If you are used to using another Rails like framework, it should feel quite familiar. The only major difference is the separation of the changeset from the model. This seems like a little extra overhead at first but for more complex situations, you may want different changesets for different actions. In this case, the distinct changeset is very valuable.

In my next post, I will integrate this simple API with and Elm front end and demonstrate how we can extend that validation logic to perform uniqueness checking on top of the existing validations.

--

--

billperegoy
im-becoming-functional

Polyglot programmer exploring the possibilities of functional programming.