POST Courses

Let's include an endpoint to create a course.

Create Course
HTTP MethodPOST
API Endpoint/api/courses
Request Path Parameter
Request Query Parameter
Request BodyJSON object (course attributes)
Response BodyJSON object (created course)
Response Status201

Notes:

  • We use a POST method to create a course.
  • The path (endpoint) is similar to the GET request we had earlier for receiving all courses. You can think about it as we are going to post to the collection of courses here (whereas before we were going to read the collection of courses).
  • We need to provide the attributes (offering code & title) for creating a course. These attributes will be provided in the "body" (payload) of the request. (We will soon see what that means!)
  • Once a course is created, we will return the newly created resource. This is just a common pattern in the design of APIs.
  • We send a status code of 201 to signal the resource creation succeeded.

Here are unit tests for testing this API endpoint:

@Test
public void postCourseWorks() throws UnirestException {
  // This test will break if "EN.601.421" is already in database
  Course course = new Course("EN.601.421", "Object-Oriented Software Engineering");
  final String URL = BASE_URL + "/api/courses";
  HttpResponse<JsonNode> jsonResponse = Unirest.post(URL)
      .body(gson.toJson(course)).asJson();
  assertEquals(201, jsonResponse.getStatus());
  assertNotEquals(0, jsonResponse.getBody().getArray().length());
}
@Test
public void postCourseWithIncompleteData() throws UnirestException {
  Map<String, String> course = Map.of("title", "Made-up Course");
  final String URL = BASE_URL + "/api/courses";
  HttpResponse<JsonNode> jsonResponse = Unirest.post(URL)
      .body(gson.toJson(course)).asJson();
  assertEquals(500, jsonResponse.getStatus());
}
@Test
public void postCourseThatAlreadyExist() throws UnirestException {
  // This test will break if "EN.601.226" is not in the database
  Course course = new Course("EN.601.226", "Data Structures");
  final String URL = BASE_URL + "/api/courses";
  HttpResponse<JsonNode> jsonResponse = Unirest.post(URL)
      .body(gson.toJson(course)).asJson();
  assertEquals(500, jsonResponse.getStatus());
}

Here are the Postman requests corresponding to the above tests:

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

post("/api/courses", (req, res) -> {
  try {
    Course course = gson.fromJson(req.body(), Course.class);
    courseDao.create(course.getOfferingName(), course.getTitle());
    res.status(201);
    return gson.toJson(course);
  } catch (DaoException ex) {
    throw new ApiError(ex.getMessage(), 500);
  }
});

Notice how I have set the status code.

Also, notice how I have used req.body object to get the attributes of the course. The client is expected to provide these attributes as a JSON data in the request body.

Run the tests in IntelliJ and make sure they pass.

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

If you pay close attention to the error messages, you'll notice there is too much information in there! We are exposing to our client e.g. the structure of our internal database. This is not good! The error messages should not be too detailed (just as they should not be too generic). Ideally, you process the error messages and come up with a message that meets the a middle-ground. For simplicity, we'll leave this as is.