DEV Community

Cover image for A mini-guide — Build a REST API as a Go microservice together with MySQL
Johan Lejdung
Johan Lejdung

Posted on • Updated on

A mini-guide — Build a REST API as a Go microservice together with MySQL

I have recently found myself coding and deploying a lot of Go microservices, both at my day-job at Storytel and for my side-project Wiseer. In this mini-tutorial, I will create a simple REST-API with a MySQL database. The code for the complete project will be linked at the end of the article.

If you haven’t already done so I recommend taking the Go Tour as a complementary to this article.

Let’s dive in!

Setup the API

One of the first things to do is to choose a routing package. Routing is what connects an URL to an executable function. The mux package has served me well for routing, but there are other alternatives as well, with no real difference in performance. Such as httprouter and chi. I will be using mux in this guide.

For the sake of simplicity, we will create a single endpoint that prints out a message.

The code above creates a Router, associates a URL with an handler function — postFunction in our case — and starts a server on port 8080 using that Router.

Easy enough, huh? 🤠

Database connection

Let's build on the code above by connecting it to a MySQL database. Go provides an interface for SQL databases but requires a driver. I’m using go-sql-driver for this example.

The code is placed in another package, called db, and assumes there is a database running on localhost:3306 with a database called demo. The returned database automatically handles a connection pool to the database.

Let us update the postFunction from the previous code-snippet, to use this database.

That’s really it! It’s rather simple, but there are a few issues with the code above and a few nice-to-haves missing. This is where it gets a bit trickier, but don't jump ship just yet! ⚓️

Structs & Dependencies

If you’ve examined the code above, you might have noticed that we are opening the database on each API-call; even though the opened database is safe for concurrent use. We need some dependency management to make sure we only open the database once, for this, we want to use a struct.

We start by creating a new package, called app, to host our struct and it’s methods. Our App struct has two fields; a Router and a Database accessed at ln 17 and ln 24. We also set the returned status code manually at the end of the method at ln 30.

The main package and function also needs a few changes to make use of the new App struct. We remove the postFunction and setupRouter functions from this package, since it’s now in the app package. We are left with:

To make use of our new struct, we open a database and a new Router. Then we insert both of them into the fields of our new App struct.

Congratulations! You now have a connection to your database that will be used concurrently across all of your incoming API-calls 🙌

As a final step, we will add a GET-Method to our router setup and return the data in JSON. We start by adding a struct to fill our data with, and map the fields to JSON.

We follow that up with an expansion of the app.go file, with a new method getFunction that fetches and writes the data to the client response. The final file looks like this.

Database Migrations

We are going to add a final addition to the project. When a database is tightly coupled with an application or service you can save yourself unfathomable levels of headache by properly handling migrations of said database. We are going to use migrate for that, and expand our db package.

I'll go through the following, and admittedly long, gist below the embedded code.

Right after the opening of the database, we add another function call to migrateDatabase that will in turn; start the migration process.

We will also add a MigrationLogger struct to handle the logging during the process, the code can be seen here and it's usage on ln 45.

The migrations are executed from normal sql-queries. The migration files are read from the folder seen at ln 37.

Each time the database is opened, all the unapplied database migrations will be applied. Thereby keeping the database up to date without any manuainterventionon.

This coupled with a docker-compose file - containing the database - makes development on multiple machines dead-simple.

Wrapping it up

So you've made it all the way down here 👏 👏

An undeployable microservice is of no use, therefore we will add a Dockerfile to package the application for easy distribution - then I promise to let you go.

The built image is a mere 10MB! 😱

The code is available below.


Before cloning

Read the accompanying article here:

Change the module name if you are cloning it to a new location.

go mod init <your_module_name>

If you are using a private repository make sure to set GOPRIVATE with:

go env -w


Included here are a docker-compose file that can be used to spin up a MySQL database in docker for the porpose of running this code.

Run the code:

go run main.go

Try inserting a value with

curl -XPOST localhost:8080/endpoint -v

Then fetching a value with

curl localhost:8080/endpoint/1 -v

Further reading

If you are interested in optimal folder structure, have a look at this

I hope that you found this interesting and that you learned something! There are of course still things to improve upon, but that's where you and your creativity comes in 👍

If you liked this article sharing it with friends or on Twitter is greatly appreciated!

Liquid error: internal

I plan to cover more advanced topics in - hopefully shorter 😅 - articles. The topics I have thought of so far are; middleware usage, testing, dependency injection and service layers.

Discussion (4)

searlashorn profile image
Oleksandr Rybalov

Awesome work. I like it!

johanlejdung profile image
Johan Lejdung Author

Thank you, it means a lot!

raulismasiukas profile image
Raulis Masiukas

Great article, thanks Johan!

johanlejdung profile image
Johan Lejdung Author

Thank you Raulis, means a lot! :)