Exercise Thursday

Awesome. We are well on our way to finishing our API routes. We'll soon add the rest of our routes to add Foodtypes and Reviews to our database that will be accessible via our API.

But looking at our code in our App.java, you can already see that we have started to become a little less DRY: All routes have some of the same code in them; res.type("application/json") for example.

Let's make our routes a little more efficient before we begin adding more. We can accomplish this by using filters.

Adding Filters to our Routes

Filters are code blocks that Spark can execute before, after, and after after (after after filters run after an after filter) receiving a request. They allow us to easily take care of busywork and cut down on repetitive code. They are fairly simple to implement.

Here is our current App.java code with one filter in place:

App.java
public class App {

   public static void main(String[] args) {
       Sql2oFoodtypeDao foodtypeDao;
       Sql2oRestaurantDao restaurantDao;
       Sql2oReviewDao reviewDao;
       Connection conn;
       Gson gson = new Gson();

       String connectionString = "jdbc:h2:~/jadle.db;INIT=RUNSCRIPT from 'classpath:db/create.sql'";
       Sql2o sql2o = new Sql2o(connectionString, "", "");
       restaurantDao = new Sql2oRestaurantDao(sql2o);
       foodtypeDao = new Sql2oFoodtypeDao(sql2o);
       reviewDao = new Sql2oReviewDao(sql2o);
       conn = sql2o.open();

       //CREATE
       post("/restaurants/new", "application/json", (req, res) -> {
           Restaurant restaurant = gson.fromJson(req.body(), Restaurant.class);
           restaurantDao.add(restaurant);
           res.status(201);;
           return gson.toJson(restaurant);
       });

       //READ
       get("/restaurants", "application/json", (req, res) -> {
           return gson.toJson(restaurantDao.getAll());
       });

       get("/restaurants/:id", "application/json", (req, res) -> {
           int restaurantId = Integer.parseInt(req.params("id"));
           return gson.toJson(restaurantDao.findById(restaurantId));
       });

       //FILTERS
       after((req, res) ->{
           res.type("application/json");
       });
   }
}

This has two benefits - our code is slightly shorter, and we don't have to remember to set the response type. The more routes we add, the more we'll benefit from having this in place.

Let's add some more routes to App.java and test them with Postman to make sure that they work. If you feel confused about which routes you need to implement, refer back to your DAO files, as those contain the methods we agreed to implement.

Our GET and POST for Foodtypes should feel fairly straightforward at this point, but we'll need to get a little bit fancy to handle one-to-many relationships between Restaurants and Reviews, and creating many-to-many relationships will also require some rewriting.

Try and see if you can add in as many of these simple routes on your own, testing as you go with Postman. Check the cheat sheet for some JSON samples for different data models.

Our One-to-Many Route

Next, let's tackle our one-to-many route. Because a Review shouldn't exist without a link to a parent Restaurant object. Looking at our Review data model, it seems clear that we need information for the writtenBy, rating, and restaurantId properties.

But where do we get the restaurantId? It seems a bit clunky to just expect a user to know it when they make a request. As we already know, we can retrieve the id of the restaurant from the URL like this: int restaurantId = Integer.parseInt(req.params("restaurantId")); so, we can use this information to build our Review object before we save it.

App.java
...

post("/restaurants/:restaurantId/reviews/new", "application/json", (req, res) -> {
    int restaurantId = Integer.parseInt(req.params("restaurantId"));
    Review review = gson.fromJson(req.body(), Review.class);

    review.setRestaurantId(restaurantId); //we need to set this separately because it comes from our route, not our JSON input.
    reviewDao.add(review);
    res.status(201);
    return gson.toJson(review);
});
...

The only real difference here is that while Gson is able to make a Review object from the JSON that we are supplying it, it is missing an important part - therestaurantId is not in the JSON, but in the route parameter. We can deal with this by making a quasi-incomplete Review object first, and then setting the id correctly with our setter immediately afterward. (An important thing to know is that the Gson library does not call an object's constructor, but sets an Object's properties via a process called reflection. We don't address reflection in this class as it is a complicated topic, but it is worth the time if you are interested in researching it.)

If you are still unclear as to why this is necessary, pop a breakpoint at the beginning of the route and run this code in debugger mode, firing a request to create a new Review with Postman, and you'll see that a Review object is created by the JSON, but doesn't have a valid restaurantId until we set one.

Filters are just one cool thing you can use to make your app code clearer and more concise. You can also use redirects to make custom 404 pages, handle 500 server errors gracefully, set and remove cookies, and much much more, using the same simple syntax that we are now getting very comfortable with.

Let's finish building out our routes.

Try and build out as many routes as you can on your own - use the methods descibed in your interface as your guide. Refer to the example repo at the bottom of this page if you get stuck. If you would like to test as you go, there are some handy JSON snippets listed in the cheat sheet.

These are the routes we'll need to build out in our App.java file:

  get("/restaurants"
  get("/restaurants/:id"
  post("/restaurants/new"
  post("/foodtypes/new"
  get("/foodtypes"
  get("/restaurants/:id/reviews"
  post("/restaurants/:restaurantId/reviews/new

We'll work on the two missing routes, post("/restaurants/:restaurantId/foodtype/:foodtypeId" that associates foodtypes with restaurants as a many-to-many relationship in an upcoming lesson, and we'll also add a route to retrieve all restaurants of a specific foodtype, and all foodtypes associated with a specific restaurant, which would be the get(/foodtypes/:foodtypeId/restaurants route and get(/restaurants/:restaurantId/foodtypes, so don't worry about those for now.

Tip:

With a little bit of branching and the following code:

return "{\"message\":\"I'm sorry, but no restaurants are currently listed in the database.\"}"; it is easy to output a message to a user if no data was found. Consider adding this to your routes as well.

Example GitHub Repo for Jadle Code at this stage

Here are some JSON snippets you can use to test your database:

Restaurant: ```json

{
    "name": "Pita Pete",
    "address": "342 SW Taylor",
    "zipcode": "97202",
    "phone": "972-412-9874",
    "website": "http://www.petes.com",
    "email": "[email protected]"
}

Review: { "writtenBy": "Snarky Steve", "rating": "1", "content": "it was 'great'" }

Foodtype:

{ "name": "Italian" } ```

Unchecked Exception List

  • ArrayIndexOutOfBoundsException
  • ClassCastException
  • IllegalArgumentException
  • IllegalStateException
  • NullPointerException
  • NumberFormatException
  • AssertionError
  • ExceptionInInitializerError
  • StackOverflowError
  • NoClassDefFoundError

Checked Exception List

  • Exception
  • IOException
  • FileNotFoundException
  • ParseException
  • ClassNotFoundException
  • CloneNotSupportedException
  • InstantiationException
  • InterruptedException
  • NoSuchMethodException
  • NoSuchFieldException

The list above does not include Exceptions created by add-on libraries such as Sql2o, h2, Gson, etc.