We’ve managed to implement a full set of CRUD operations on our Project type. Still, our app is definitely not perfect – for example, in the previous post we’ve implemented DELETE handler as Html Handler. That’s not exactly fine, since browsers generally use only GET and POST – other methods (such as DELETE) are used either by web services or by AJAX calls. We used it as AJAX call, and that’s why we can’t exactly use typical redirection, and instead we redirected the user by hardcoded link. This lead to races and projects which were just deleted were sometimes visible. In this post we’ll deal with this problem.

We’re going to remove the hardcoded link and instead redirect user to link returned from API call, after results are returned. To do this, we need to make several changes:

  1. add a Handler for JSON DELETE requests
  2. remove redirection from button
  3. add redirection to AJAX handler
  4. change request to accept application/json header

First one is critical for us, so let’s start with it. We’ll provide only DELETE method – this way we’ll have something to implement when discussing high-level testing.
Right now we want to provide only JSON responses. Since Yesod uses Aeson library under the hood for JSON handling, its types will also appear in the handler (Aeson uses Value type to denote JSON). New implementation of deleteProjectR looks like this:

deleteProjectR :: ProjectId -> Handler TypedContent
deleteProjectR projectId = selectRep $ do
    provideRep $ do
      _ <- runDB $ delete projectId
      renderUrl <- getUrlRender
      returnJson $ object ["target" .= (renderUrl ProjectsR)]

There are quite a few new elements in here! First one is the signature – Handler TypedContent. TypedContent means that Yesod will check headers and verify whether it can provide a representation required by the client (by default it’s not checked). For example, if client requests application/xml, but we can only provide application/json, a 406 (Not Acceptable) error will be returned. This data type check is done by selectRep function, basing on provideRep calls. There is a little trick here – actual delete is performed in provideRep call. That’s not perfect, as it has nothing to do with the representation, and we would prefer to execute the action always if we can provide the return type. For now it’s not a big deal since we have just one representation, but this can lead to problems (code duplication mostly) later on, when we’ll have many representations. Creating the JSON is done in a bit simplistic way (to make it totally clean we should define a data type, a ToJson instance etc.), but doing it in more sophisticated way seems like an overkill.

Anyway, we’ve done our job on the server, and everything still works – that’s because jQuery AJAX calls accept all possible responses (Accept: */* header). We want to change it, since we’ll be only able to handle JSON responses – thus our templates/project.julius will change to:

jQuery("#delete-project").on("click", function() {
  jQuery.ajax("@{ProjectR projectId}", {
    method: "DELETE",
    headers: { Accept: "application/json; charset=utf-8" }
  }).done(function(response) {
    window.location.replace(response.target);
  });
});

This will redirect you to proper page each time after deletion, thus there will be no race condition. Only thing left is to change the hyperlink to actual button:

<button href=@{ProjectsR} .btn .btn-danger #delete-project>Delete

in project.hamlet. And it’s done!

We’ve implemented our first JSON API. It might not be very impressive, but works well – at least better than the previous solution, which had a built-in race condition.
That’s it for today! Since we’ve already implemented quite a lot of stuff, we would like to be sure that it keeps working after each change. This is what the next post will focus on – testing environment preparation and testing the application. This will be just a short instruction of how to manually prepare a single environment for testing – automatically generating and deploying such environments is a much harder topic and we won’t be focusing on it now. Perhaps later, who knows? Maybe we’ll even have a series on environment automation!

Stay tuned!