Building and Testing a REST API in GoLang using Gorilla Mux and MySQL

Image for post
Image for post

In this tutorial we will learn how to build and test a simple REST API in Go using Gorilla Mux router and the MySQL database. We will also create the application following the test-driven development (TDD) methodology.


  • Become familiar with the TDD methodology.
  • Become familiar with the Gorilla Mux package.
  • Learn how to use MySQL in Go.


  • You must have a working Go and MySQL environments.
  • Basic familiarity with Go and MySQL.

About the Application

The application is a simple REST API server that will provide endpoints to allow accessing and manipulating ‘users’.

API Specification

  • Create a new user in response to a valid POST request at /user,
  • Update a user in response to a valid PUT request at /user/{id},
  • Delete a user in response to a valid DELETE request at /user/{id},
  • Fetch a user in response to a valid GET request at /user/{id}, and
  • Fetch a list of users in response to a valid GET request at /users.

The {id} will determine which user the request will work with.

Creating the Database

As our application is simple, we will create only one table called users with the following fields:

  • id : is the primary key.
  • name : is the name of the user.
  • age : is the age of the user.

Let’s use the following statement to create the database and the table.

CREATE DATABASE rest_api_example;
USE rest_api_example;

It is a very simple table but it’s ok for this example.

Getting Dependencies

Before we start writing our application, we need to get some dependencies that we will use. We need to get the two following packages:

  • mux : The Gorilla Mux router.
  • mysql : The MySQL driver.

You can easily use go get to get it:

go get
go get

Getting Started

First of all, let’s create a file called app.go and add an App structure to hold our application. This structure provides references to the router and the database that we will use on our application. To make it testable let’s also create two methods to initialize and run the application:

// app.go

package main

import (

The Initialize method is responsible for create a database connection and wire up the routes, and the Run method will simply start the application.

Note that we have to import both mux and mysql packages here.

Now, let’s create the main.go file which will contain the entry point for the application:

// main.go

package main

func main() {
a := App{}
// You need to set your Username and Password here
a.Initialize("DB_USERNAME", "DB_PASSWORD", "rest_api_example")


Note that on this step you need to set the username and password.

Now, let’s create a file called model.go which is used to define our user structure and provide some useful functions to deal with database operations.

// model.go

At this point we should have a file structure like that:

┌── app.go
├── main.go
└── model.go

Now it’s time to write some tests for our API.

Writing Tests

As we are following the test-driven development (TDD) methodology, we need to write the test even before we write the functions itself.

As we will run the tests using a database, we need to make sure the database is set up before running the tests and cleaned up after the tests. So let’s create the main_test.go file. In the main_test.go file let’s create the TestMain function which is executed before all tests and will do these stuff for us.

// main_test.go

Note that the global variable a represents the application that we want to test.

We use the ensureTableExists function that the table we need for testing is available. The tableCreationQuery is a constant which is a query used to create the database table.

After run the tests we need to call the clearTable function to clean the database up.

In order to run the tests we need to implement the Initialize function in the app.go file, to create a database connection and initialize the router. Now the Initialize function should look like this:

// app.go

func (a *App) Initialize(user, password, dbname string) {
connectionString := fmt.Sprintf("%s:%s@/%s", user, password, dbname)

At this point even if we don’t have any tests we should be able to run go test without finding any runtime errors. Let’s try it out:

go test -v

Executing this command should result something like this:

testing: warning: no tests to run
ok _/home/user/app 0.051s

Writing API Tests

Let’s start testing the response of the /users endpoint with an empty table.

// main_test.go

This test will delete all records in the users table and send a GET request to the /users endpoint.

We use the executeRequest function to execute the request, and checkResponseCode function to test that the HTTP response code is what we expect, and finally, we check the body of the response and check if it is what we expect.

So, let’s implement the executeRequest and checkResponseCode functions.

// main_test.go

func executeRequest(req *http.Request) *httptest.ResponseRecorder {
rr := httptest.NewRecorder()
a.Router.ServeHTTP(rr, req)

return rr

Make sure you have imported the "net/http" and "net/http/httptest" packages and run the tests again. If everything goes well you should get something like this:

=== RUN   TestEmptyTable
--- FAIL: TestEmptyTable (0.02s)
main_test.go:71: Expected response code 200. Got 404
main_test.go:58: Expected an empty array. Got 404 page not found
exit status 1
FAIL _/home/user/app 0.055s

As expected, the tests will fail because we don’t have implemented anything yet, so let’s continue implementing other tests before we implement the functions for the application itself.

Let’s implement a test that tries to fetch a nonexistent user.

// main_test.go

func TestGetNonExistentUser(t *testing.T) {

This test basically tests two things: the status code which should be 404 and if the response contains the expected error message.

Note that in this step we need to import the "encoding/json" package to use the json.Unmarshal function.

Now, let’s implement a test to create a user.

// main_test.go

In this test, we manually add a new user to the database and, by accessing the correspondent endpoint, we check if the status code is 201 (the resource was created) and if the JSON response contains the correct information that was added.

Note that in this step we need to import the "bytes" package to use the bytes.NewBuffer function.

Now, let’s implement a test to fetch an existing user.

// main_test.go

func TestGetUser(t *testing.T) {

This test basically add a new user to the database and check if the correct endpoint results in an HTTP response with status code 200 (success).

In this test above we use the addUsers function which is used to add a new user to the database for the tests. So, let’s implement this function:

// main_test.go

func addUsers(count int) {
if count < 1 {
count = 1

Note that in this step we need to import the "strconv" package to use the strconv.Itoa function to convert an integer to a string.

Now, let’s test the update option:

// main_test.go

In the above test, we basically add a new user to the database and then we use the correct endpoint to update it.

It tests if the status code is 200 indicating success and if the JSON response contains the updated details about the user.

And the last test, for now, will try to delete a user.

// main_test.go

func TestDeleteUser(t *testing.T) {

In this test we basically create a new user and test if it exists in the database, then we user the correct endpoint to delete the user and checks if it was properly deleted.

At this point we should be able to run go test -v in your project directory.

All tests should fail but it’s ok because we did not implement the application functions yet. So let’s implement it to make these tests pass.

Creating the Application Functionalities

Let’s begin implementing the methods in the model.go file. These methods are responsible for executing the database statements and it can be implemented as follows:

// model.go

func (u *user) getUser(db *sql.DB) error {
statement := fmt.Sprintf("SELECT name, age FROM users WHERE id=%d", u.ID)
return db.QueryRow(statement).Scan(&u.Name, &u.Age)

The getUsers function fetches records from the users table and limits the number of records based on the count value passed by parameter. The start parameter determines how many records are skipped at the beginning.

At this point, we need to remove the errors package and import the fmt package.

The model is done, now we need to implement the App functions, including the routes and route handlers.

Let’s start creating the getUser function to fetch a single user.

// app.go

func (a *App) getUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid user ID")

This handler basically retrieves the id of the user from the requested URL and uses the getUser function, from the model, to fetch the user details.

If the user is not found it will respond with the status code 404. This function uses the respondWithError and respondWithJSON functions to process errors and normal responses. These functions are implemented as follows:

// app.go

func respondWithError(w http.ResponseWriter, code int, message string) {
respondWithJSON(w, code, map[string]string{"error": message})

The rest of the handlers can be implemented in a similar manner:

// app.go

func (a *App) getUsers(w http.ResponseWriter, r *http.Request) {
count, _ := strconv.Atoi(r.FormValue("count"))
start, _ := strconv.Atoi(r.FormValue("start"))

This handler uses the count and start parameters from the querystring to fetch count number of users, starting at position start in the database. By default, start is set to 0 and count is set to 10. If these parameters aren’t provided, this handler will respond with the first 10 users.

Let’s implement the handler to create a user.

// app.go

func (a *App) createUser(w http.ResponseWriter, r *http.Request) {
var u user
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&u); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
defer r.Body.Close()

This handler assumes that the request body is a JSON object containing the details of the user to be created. It extracts that object into a user and then uses the createUser function.

The handler to update a user:

// app.go

func (a *App) updateUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid user ID")

This handler extracts the user details from the request body and the id from the URL, and uses the id and the body to update the user.

And the last handler that we will implement is used to delete a user.

// app.go

func (a *App) deleteUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid User ID")

This handler extracts the id from the URL and uses it to delete the corresponding user.

Now that we have all handlers implemented we must define the routes which will use them.

// app.go

func (a *App) initializeRoutes() {
a.Router.HandleFunc("/users", a.getUsers).Methods("GET")
a.Router.HandleFunc("/user", a.createUser).Methods("POST")
a.Router.HandleFunc("/user/{id:[0-9]+}", a.getUser).Methods("GET")
a.Router.HandleFunc("/user/{id:[0-9]+}", a.updateUser).Methods("PUT")
a.Router.HandleFunc("/user/{id:[0-9]+}", a.deleteUser).Methods("DELETE")

The routes are defined based on the API specification defined earlier. The {id:[0-9]+} part of the path indicates that Gorilla Mux should treat process a URL only if the id is a number. For all matching requests, Gorilla Mux then stores the the actual numeric value in the id variable.

Now we just need to implement the Run function and call initializeRoutes from the Initialize method.

// app.go

func (a *App) Initialize(user, password, dbname string) {
connectionString := fmt.Sprintf("%s:%s@/%s", user, password, dbname)

Remember to import all packages needed.

// app.go

import (

The final version of the app.go file should look like this:

Now if we run the tests again:

go test -v

We should get the following results:

=== RUN   TestEmptyTable
--- PASS: TestEmptyTable (0.02s)
=== RUN TestGetNonExistentUser
--- PASS: TestGetNonExistentUser (0.02s)
=== RUN TestCreateUser
--- PASS: TestCreateUser (0.01s)
=== RUN TestGetUser
--- PASS: TestGetUser (0.01s)
=== RUN TestUpdateUser
--- PASS: TestUpdateUser (0.01s)
=== RUN TestDeleteUser
--- PASS: TestDeleteUser (0.01s)
ok 0.124s

The complete code can be found on Github at the following link:

Travis CI and Coveralls

If you are familiar with Travis CI and Coveralls you can use the following settings for the build environment on the .travis.yml file:

language: go

Note that for the Travis CI run the tests properly using the MySQL database you need to set the username as root and leave the password empty.

If you are not familiar with it, I suggest starting reading the Getting Started section of both Travis CI and Coveralls. These tools are well documented and quite easy to understand and use.

If you want to manually test the API by manually sending requests I suggest to use the Insomnia application. It is a cross-platform REST API client that is very easy to use. It can be found here:


Almost all this tutorial was created (and some codes copied) based on the tutorial written by Kulshekhar Kabra which can be found at the following link:

Written by

Machine Learning Engineer and IPCV Researcher

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store