API 101: The basics of building a RESTful API

Johanne Andersen
6 min readSep 15, 2019

--

As mentioned in the last post, it’s time to build a RESTful API!

REST err RESTful since we’re adding functionality

Now I know what you might be thinking, wait a second, weren’t we talking about REST APIs? What is this RESTful thing all of a sudden? Well, it turns out that conforming completely to REST is kind of difficult and not very practical.

Considering that the architecture was proposed in 2000 and most web pages are not only displaying HTML anymore, some things just don’t make sense for the web today. Hence the term RESTful was coined. Usability takes precedence over adhering completely to the constraints, but the main principles of REST are still respected.

A suggested strategy by Apigee is to first implement your API using data stubs [1]. So there is no need to wire up an entire backend server. You only want to implement the interface you use for the API so you can start getting feedback on it.

So that’s what we’ll do for our Todo API we specified in the previous blog post.

Implementing the specification

We defined our three endpoints as follows:

/users/todo-lists/todo-items

Using Kotlin and Ktor to implement this API gives us three routes, each with their own repository. Here’s the implementation for the /todo-list endpoint :

First, we have to specify the structure of a todo list. This is the representation of a todo list in our API, not necessarily the representation of the data we would store in the database about a todo list.

data class TodoList(
val self: String,
val id: Int,
var name: String, // Can be changed using PATCH, so it is a var
val userId: Int,
val userLink: String,
val createdDate: LocalDate,
val todoItems: List<TodoItem>
)

So in order to allow our users to interact with this resource, we need to define the endpoints in our spec.

fun Routing.todoList() {
val todoListRepository = TodoListRepository()
route("$API_PREFIX/todo-lists") {
get("/") {
call.respond(todoListRepository.findAll())
}
get("/{id}") {
val id = call.parameters["id"]!!.toInt() // Kotlin notation to indicate
// that the value cannot be null
val list = todoListRepository.find(id)
call.respond(list)
}
post("/") {
val body = call.receive<TodoListPayload>()
call.respond(todoListRepository.create(body))
}
delete("/{id}") {
val id = call.parameters["id"]!!.toInt()
call.response.status(HttpStatusCode.OK)
}
patch("/{id}") {
val id = call.parameters["id"]!!.toInt()
val body = call.receive<TodoListPayload>()
val list = todoListRepository.update(id, body)
call.respond(list)
}
}
}

Here the TodoListPayload represents the fields we need to both create and update the todo list resource.

data class TodoListPayload(
val name: String
)

You might have noticed the $API_PREFIX in front of the route. This is a constant that is used to indicate the version of the API. In this case, the value is /api/v1 to indicate to the user that they are using version 1. Using a version specifier makes it easier to upgrade the API when that time comes and it allows you to support multiple versions, should that be necessary.

Status codes

Another very important REST property is the use of HTTP response codes to indicate the status of the request. You should always always use these since they convey important information to your users.

So here’s how we would implement the GET /todo-items/{id} to ensure that the user will know if they entered a non-existent id.

get("/{id}") {
val id = call.parameters["id"]!!.toInt()
val list = todoListRepository.find(id)if (list != null) {
call.respond(list)
} else {
call.response.status(HttpStatusCode.NotFound)
}
}

There are a bunch of HTTP status codes for almost every possible situation you could think of, which you can find here.

If you want to go one step further, you could also return a message along with the status code, so the user will know exactly what the error is.

Error handling

You also want to consider error handling more generally. What should happen if the user enters an invalid URL? You probably want to return a 404, but how exactly do you want to do it?

What if the user enters an integer in a field that expects a string? How are you going to handle that?

One thing is for sure, you want to avoid returning 500 Internal server error . It does not give your users any clue about what caused the error. Which means you have to think of all the cases where your API might break and how to deal with those elegantly.

Some common ones are:

  • Out of memory errors
  • Invalid properties
  • Wrong formatting

There are of course many many more, but they might be specific to your use case.

Health checks

Another important aspect from a maintenance perspective is having a health check endpoint, this is simply an endpoint that can be queried to determine that the API is alive. In our Todo API case, there’s an endpoint at / which just returns "Todo API" which can be used to show that the API is up and running.

Testing

One way to be sure your API behaves as you expect is to create tests. Here you have multiple options: Unit tests and integration tests. A mix of both would be best

Unit tests

Use these to test that your endpoint returns the expected resources or status codes for your endpoints. This means testing both correct and incorrect requests.

Integration tests

Use these to test the entire workflow of the API, from the request is sent until the response is received on the other end. This also means ensuring that the correct data is stored back in the database, or that errors are handled correctly. These might make more sense when you have a backend working as well.

Here Postman can be your friend. Both for unit tests and for integration test. You can even run multiple requests after one another and test that they behave as expected.

So if we have a user that creates a new todo list and adds a todo item, the tests could look like this:

This is just a small subset of what Postman can do. If you’re interested, let me know and I’ll write a post about that too.

You can also save your requests in a collection in Postman so you easily can reference them and test your API.

Here’s a checklist of the basics we’ve covered so far.

  • Test endpoints using data stubs → allows for faster feedback
  • Use status codes to indicate the status of the request
  • Consider error handling, what are you going to with unexpected behaviour? Avoid 500 Internal server error
  • Health checking
  • Write all of the tests. Both unit and integration tests.

So there you have it. The basics of how to build a RESTful API. However, we are far from done. Our todo API is severely lacking in the security department as we have no security. We also have no way of identifying users and authenticating them, which is important if you need to support login. We also have no data validation, which means the API will throw those dreaded 500 Internal server error messages that we want to avoid.

So stay tuned for next time where we dive into authentication and validation.

The code for this post can be found here.

← Previous post: Designing kickass APIs

Next post: Coming September 29th

Resources

[1]: API design: The missing link

--

--

Johanne Andersen

Software Developer, learning enthusiast and plant lover.