GET Course

Let's create a route to get a specific course given its offering code.

Read Course
HTTP MethodGET
API Endpoint/api/courses/:offeringName
Request Path ParameterofferingName
Request Query Parameter
Request Body
Response BodyJSON object (note)
Response Status200

Notice the API endpoint; to retrieve an item from a collection, it is common to use an endpoint /api/collection/:id where id is the primary key of the item you are searching for.

Here are the unit tests for testing this API endpoint:

@Test
public void getCoursesGivenOfferingName() throws UnirestException {
  final String OFFERING_NAME = "EN.601.226";
  final String URL = BASE_URL + "/api/courses/" + OFFERING_NAME;
  HttpResponse<JsonNode> jsonResponse = Unirest.get(URL).asJson();
  assertEquals(200, jsonResponse.getStatus());
  assertNotEquals(0, jsonResponse.getBody().getArray().length());
}
@Test
public void getCoursesGivenOfferingNameNotInDatabase() throws UnirestException {
  final String OFFERING_NAME = "EN.000.999";
  final String URL = BASE_URL + "/api/courses/" + OFFERING_NAME;
  HttpResponse<JsonNode> jsonResponse = Unirest.get(URL).asJson();
  assertEquals(404, jsonResponse.getStatus());
}

Notice the underlying assumption in these tests are that a course with offering name of EN.601.226 exists in the database yet a course with offering code of EN.000.999 does not.

Here are Postman requests corresponding to the above tests:

Finally, here is the route method that must be added to ApiServer.main:

get("/api/courses/:offeringName", (req, res) -> {
  try {
    String offeringName = req.params("offeringName");
    Course course = courseDao.read(offeringName);
    if (course == null) {
      throw new ApiError("Resource not found", 404); // Bad request
    }
    res.type("application/json");
    return gson.toJson(course);
  } catch (DaoException ex) {
    throw new ApiError(ex.getMessage(), 500);
  }
});

Notice how the path contains :offeringName and how I have used req.params object to get the path parameter.

Run the test (in IntelliJ). At this point the second test must fail! This is caused since we have not implemented a route handler to handle exceptions. Add the following to the ApiServer.main method, before any other route method:

exception(ApiError.class, (ex, req, res) -> {
  // Handle the exception here
  Map<String, String> map = Map.of("status", ex.getStatus() + "",
      "error", ex.getMessage());
  res.body(gson.toJson(map));
  res.status(ex.getStatus());
  res.type("application/json");
});

This method maps any ApiError thrown from another route method and maps it to a JSON object which is then attached to the response that goes to the client.

Now if you run the second test (getCoursesGivenOfferingNameNotInDatabase) again, it will pass.

Notice we always set the response content type to "application/json". We can refactor this process into a special route method; add this to the end of ApiServer.main method:

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

Make sure to run the Postman requests too and ensure the response from our server is as expected.