Learning Go by building an API

Dirk Hoekstra
Oct 11 · 6 min read

So, I wanted to learn Go for a while now and finally found the time to do so. I will attempt to build a simple REST API with Go. Our API will have 2 endpoints.

GET  /quote  # Will return a random inspirational quote
POST /quote # Will add a new inspirational quote to the database.

Users can submit inspirational quotes to the API when they are feeling inspired. And if users are in need of inspiration they can get a random inspirational quote.

Yes, this API will definitely make the world a better place.

Setting up the project

The first rabbit hole I’m diving into is how to set up a Go project. I found out that most Go programmers have a single workspace in which they store all their projects.

The first step is to set up my workspace ~/.go folder and export the GOPATH variable.

mkdir ~/.go 
export $GOPATH="${HOME}/.go"

Each workspace should have 2 folders.

  • ~/.go/src is where the source code is located.

Next, I’m creating the folder for the Quote API project in the ~/.go/src/quote-api folder.

I just want to test if nothing breaks when running the project so, for now, I create a main.go file and just print a message to the console.

The source code is located in the Go workspace under ~/.go/src/quote-api Because of this, I can use go install quote-api to compile the program.

go install quote-api
The project is setup correctly!

Setting up an API endpoint

All the things we need to set up our API endpoint can be found in the net/http package which is part of Go’s source code.

When running the server I can test the API using curl

curl localhost:8080
Welcome to the inspirational quote API homepage

Setting up a database

I want to be able to store some quotes in a database! For this project, I will use a simple file-based SQLite database. As it is easy to set up and scalability/performance is not important.

By default, Go does not support sqlite3 as a driver. To support sqlite3 I install the go-sqlite3 package by running

go get github.com/mattn/go-sqlite3

The ~/.go/pkg folder contains the source code of this dependency.

Let's write the code to set up the connection to the database and that can run queries.

In a new file database.go I put some useful functions to interact with the database.

The QueryDB() and ExecDB() functions will handle the opening and closing of a connection to the database. This way I only have to run QueryDB("SOME SQL HERE") without having to worry about all the other stuff.

I use DB Browser for SQLite to create the initial database quotes table. The table will only hold a primary id and quote field.

Note: that if this were a real-world application using database migrations would be a better way to set up the database. Checkout the golang-migrate repo if you are interested in this.

Reading JSON post data

For the next step, I want users to be able to add a quote to the database by sending a POST request to the/quotes endpoint.

In the main() function I will add a new route.

I pass the quotes() function as the handler. This function should check if the request is a POST request. For now, if this is the case I will simply print a message.

Testing this out with curl yields the following.

curl -X POST localhost:8080/quotes
POST request to /quotes endpoint!
curl -X GET localhost:8080/quotes
Invalid request method

Sweet, the new endpoint works. The next step is to retrieve a quote from the request body.

I start with creating a struct that holds the quote data. In this case, it has only 1 field: the quote that should be saved.

Next, I’m going to read the request body into this struct in the newQuote() function.

As you can see the request is read into the struct we created earlier using the json.Unmarshal() function. I add a simple check to make sure that the quote we got is not empty.

When testing the new endpoint with curl I get the following responses.

curl -X POST localhost:8080/quotes
Error: cannot unmarshal request body.
curl -X POST -H "Content-Type: application/json" -d '{"quote": "Life is either a daring adventure or nothing at all."}' localhost:8080/quotes
Quote added: "Life is either a daring adventure or nothing at all."

Parsing JSON request bodies now works.

The code is getting a bit messy though, so its time for some refactoring.

Refactoring a bit

In the newQuote() function there is a lot of error checking going on. While it works it isn’t the cleanest way to handle things. To clean it up, I’m going to refactor everything “Quote related” to a new quote.go file.

In this file, I have placed the QuoteStruct struct and the NewQuoteFromRequest() method.

I simply copied the newQuote() method from the main.go file and pasted it there — with some slight adjustments.

In the main.go file, I can now clean up the newQuote() function quite a bit.

I’m using fmt.Printf(writer, "") a lot. So let’s create a utility function that handles the writing to the response and panicking if there is an error.

We can now clean up the newQuote() function even more by using the writeResponseOrPanic() function.

Storing the quote in the database

The next step is to store the QuoteStruct in the database.

In the quote.go file, I add a new function storeInDatabase(). The parameter in front of the function means this function is called on a QuoteStruct struct — kind of like calling an object method in OOP.

In the newQuote() function in the main.go file I add the code to store the quote created from the request in the database.

Let's test it out with some good old curl requests.

curl -X POST 
-H "Content-Type: application/json"
-d '{"quote": "Life is either a daring adventure or nothing at all."}'
Quote added: "Life is either a daring adventure or nothing at all."

So far so good, and when I check the database I see that the quote is really added 🎉

Retrieving a random quote

The next step is to retrieve a random quote from the database. In the quote.go file, I add the RandomQuoteFromDatabase() method.

The SQL query SELECT quote FROM quotes ORDER BY RANDOM() LIMIT 1 will select the quote field of a single random quote in the database.

The row.Scan(&quote) will put the quote value in the quote variable. I then return a QuoteStruct struct with the quote from the database.

In the main.go file the getRandomQuote() function calls this RandomQuoteFromDatabase() and writes the quote to the response.

The final thing I have to do is update the quotes() method. This is the /quotes endpoint handler. If there is a GET request it should call the getRandomQuote() function.

I once again use curl to test my API endpoint.

curl -X GET localhost:8080/quotes
{"quote": "Life is either a daring adventure or nothing at all."}

Nice, the quote we added earlier is returned!

Returning JSON responses

Right now the API responses are a mess. Sometimes it returns JSON, sometimes it returns plain-text. And the status code is always 200 — even if there is an error.

Let's fix this up. I create a writeJson function that will write a interface{} as a JSON string to the response.

The function also sets the Content-Type: application/json header to let the receiver know we are sending JSON data.

The json.Marshal() function is a handy Go function that converts an interface{} to a JSON string.

Using this function we can clean up the getRandomQuote() function a bit.

The next step is to convert all the error messages to JSON responses as well.

I start with creating a JsonMessage struct that holds a message.

I can pass this struct to the writeJson() function that will use json.Marshal() to convert it to a JSON string. Let’s look at the updated getRandomQuote() function.

The final step is to replace all writeResponseOrPanic() functions with the new writeJson() function.

And that's it, the inspirational quote API is 100% done. If you are ever feeling down just spin up this API to get some fresh inspirational quotes 🚀

You can find the source code here - https://github.com/Dirk94/quote-api

The Startup

Medium's largest active publication, followed by +526K people. Follow to join our community.

Dirk Hoekstra

Written by

Practical programmer that likes building cool stuff!

The Startup

Medium's largest active publication, followed by +526K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade