2016-08-29

In the first part of developing a simple REST API in Go, we implemented a datastore to store our tasks. In this post, we will create a server, HTTP request handlers, and routes. We will continue to use Test Driven Development technique (TDD). However, to keep this post from becoming too long the first step "create a test and make it fail" will be skipped.

Server

Go already comes with a built-in HTTP server and a simple HTTP request multiplexer. We just need to define a route,  a handler, and start the server:

HTTP Handlers

For our API, we will develop the following three HTTP request handlers:

Get Pending tasks

Add a task

Update a task

Get Pending tasks

At first, we should create a test for getting the pending tasks from our datastore and returning them as a JSON response. As we write our test, we find out that we need to access our datastore and insert our test tasks. Let's define a global variable for our datastore:

The tasks slice of our datastore is private. We should change its visibility to public so our testing tasks can be directly inserted:

Finally, our test can be completed:

Our handler retrieves the pending tasks from our datastore, encodes, and returns them in the HTTP response body.

Let's run our test. It failed. Our field names are not in lowercase. We didn't add field tags to our Task struct so the marshaller used the same letter case as our struct. Let's fix it:

Finally, the test passes.

Interface

Our Datastore struct should not have been modified for the only purpose of testing. The tasks slice should have been remained private and not be accessed directly. Our handler should be tested by isolating it from our datastore implementation. At first, let's revert our Datastore changes:

A better solution is to create an interface:

and force our global variable to implement our Store interface:

Now our GetPendingTasks can be mocked by creating a new struct that implements our interface:

Finally, our test should be modified and should use our newly created mock:

Nice! The tests still pass.

Add a task

Now we implement our second handler for adding a task.  If the task was added, our REST service should return the HTTP status 201 Created:

and then create our handler. The JSON in the request body is decoded and the status 201 Created is written to the response header:

All our tests pass. Some error checks need to be added now. If the JSON cannot be decoded, the status 400 Bad Request should be answered. In the first part, we refactored our saveTask test to a Table Driven Test to avoid code duplication. Let's do same before implementing our new test case:

We run again the tests to be sure they still pass. Good! Now we add our test for handling decoding errors:

and add our error check to our implementation:

Allright. Another check should be added when an error is returned from our datastore. Our service should return again the status 400 Bad Request. To be able to test this case, our mock should implement the Datastore SaveTask function. At first, our SaveTask function signature is added to our Store interface:

then we implement it in our mock struct. The SaveTask should return an error only for our new test case and not for the previous ones. We add a function field to our mock struct. The test will call this function only if the function was implemented in our mock:

The saveFunc is added to our anonymous struct table and implemented in our datastore error test case:

Our mock should use the saveFunc from the test case:

Let's modify the handler and return a bad request answer when an error is returned from the datastore:

Another case need to be verified: If our the title is empty, a 400 Bad Request should be returned:

Finally, we add it to our implementation:

Update a task

This handler is quite similar to our AddTask handler so the code snippets are provided without any explanation :

A small refactor was done, the task title check was extracted to a function so it can be used in our AddTask and UpdateTask handlers:

Router

Finally, let's define our routes:

/tasks/pending GET Return the pending tasks

/tasks POST Add a new task

/tasks/{id} PUT Update an existing task

Unfortunately, the default Server Mux cannot handle dynamic routes such as /tasks/{id}. Let's implement our own router. At first, we create a Table Driven test with different test cases for our routes:

Our router contains a slice of routes. Routes are added to the router by passing a regex pattern, a handler, and a HTTP method as parameters to the HandleFunc. When a request is received, the server multiplexer will call the ServeHTTP function. This function loops over the routes slice and check if a route matches the HTTP method and the regex pattern. If a route is found, the route handler is called else a status 404 Not Found is returned:

Wrapping up

Finally, we should declare our router, our routes, and start our server in our main function:

Voilà, the server is started and ready to serve the routes that were defined above. Requests should be sent to localhost:8080.

This conclude the development of a simple REST API using Test Driven Development in Go. Again, Go already includes everything needed to create a simple HTTP server, mock, and test our implementations. Our router implementation is not so efficient but good enough for our example. If more complex routing or performance are needed, an advanced router, such as Gorilla Mux or HttpRouter, could be used.

Depending on interest, future posts may look at a more advancing examples such as:

passing our Store interface as a parameter to our HTTP handler instead of using a global variable

using a SQL database such as PostgreSQL

replacing our custom router by Gorilla Mux

The final source code can be found on GitHub.

The featured image is based on the Go mascot designed by Renee French.

Show more