DEV Community

Cover image for Write a Simple REST API in Golang
Lucas Neves Pereira
Lucas Neves Pereira

Posted on • Edited on

Write a Simple REST API in Golang

Hello there 😀

In this brief article I will explain how I write a REST API using Golang trying to follow some best practices.

The source code to follow along if needed is here.

Let's do this 👍🏻

As an example we'll be creating a very simple Post API.

Structure the project

Let's jump to our terminal and create a new directory for our project and then init a go module.



mkdir postapi
cd postapi
go mod init postapi


Enter fullscreen mode Exit fullscreen mode

Note: Best practice for module naming is to use <domain>/<nameOfApp>

Now that we have this let's open our project in our favorite code editor and create a main.go file with a package main. This will be the entry point of our application.

Next, create a package app with a app.go file

Capture d’écran 2021-04-17 à 19.01.50

Inside of it we are going to create an ** App struct** that will represent the structure of our app. There will be two fields on this struct, our app is going to have a DB and a Router.
The router will be a gorilla/mux router, so let's go ahead and and import it:

go get -u github.com/gorilla/mux

Let's add it to our struct
Capture d’écran 2021-04-17 à 19.13.02

Now we are gonna create a New() func that will be in charge of returning the actual application based on our struct.

Capture d’écran 2021-04-17 à 19.15.15

Now in our main.go file we can call this function and create a new app.
Capture d’écran 2021-04-17 à 19.19.39

Okay, the entry point of the app is structured, let's continue with the routing.

Routing

Let's use the Router of our App that we defined before to implement basic routing.
In our app.go file we are going to create an InitRoutes func that will be called inside of our New() func when we create the application. This function will be a receiver of our App. Actually a lot of our methods we will be receivers from our app from now. Since our Router fields in our struct App is of type gorilla/mux router we have access to it's methods.
Capture d’écran 2021-04-17 à 19.26.38

Let's very quickly replace the nil with a simple handler for our index route. For that create a handlers.go file in our package app. This is where we are going to store the handlers for our routes. I am going to create an IndexHandler() handler that will return an http.HandlerFunc printing a response "Welcome to Post API".
Capture d’écran 2021-04-17 à 20.29.51

Now let's call this handler in our route.
Capture d’écran 2021-04-17 à 20.32.30

Perfect, to test this out, we need to go back to the main.go file to serve the app on a port of our choice and the redirect it to our app Router. Also I've made a check function to print help with error handling.
Capture d’écran 2021-04-17 à 20.37.45

Ok, now we can run our app with go run main.go and curl the endpoint to see if we get a response.

curl http://localhost:9000

Capture d’écran 2021-04-17 à 21.51.19

Cool, routing seems to be working!

Database

It is time to setup our database, I have chosen to go with PostgreSQL, to avoid local setup I'll be running my database on a Docker container.

I'll be using the following command:



docker run --name postapidb --env POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres


Enter fullscreen mode Exit fullscreen mode

Note: To stop you just need to run docker stop postapidb and to remove it docker rm postapidb

Okay, now we have a database running let's jump to the code and create a new package database inside of our app package.

Let's create a db.go file and inside we are going to have an interface. I am going to call it PostDB, this interface it's like a contract (methods to implement) that we have to respect if we want our database to be a PostDB database. Let's start by saying that a PostDB should implement a Open() and a Close method that can return an error.

We are also going to have a DB structure like for our app that will have a single field db of type sqlx DB that is like a superset of database/sql from the Go standard library. Let's run a go get for sqlx and lib/pq (postgres sql driver) and then implement this.

go get -u github.com/jmoiron/sqlx
go get -u github.com/lib/pq

Capture d’écran 2021-04-17 à 21.13.57

Now before writing the body of our Open() and Close() we can add a database fields to our App struct in app.go saying we are using a PostDB in this app 🙂

Capture d’écran 2021-04-17 à 21.17.00

Moving on, starting with our Open method let's open a new connection to our postgres database.

Capture d’écran 2021-04-17 à 21.24.52

To establish this connection, the second argument of the sqlx.Open() method is a postgres connection string. Let's build this string in a seperate file called config.go still in the database package.

Capture d’écran 2021-04-17 à 21.27.58
Capture d’écran 2021-04-17 à 21.28.38

Another thing I want to do is create a SQL schema that will be run after the connection. This schema will just create a table posts in our db in case there is not one. Let's add this to a schemas.go file.

Capture d’écran 2021-04-17 à 23.00.14

Capture d’écran 2021-04-17 à 21.33.21

Our Close method will be a lot simpler. We just need call the method Close from sqlx

Capture d’écran 2021-04-17 à 21.35.26

Before writing some methods for our data, let's jump to the main.go file and init the connection to the database.

Capture d’écran 2021-04-17 à 21.48.00

Let's run go run main.go

Capture d’écran 2021-04-17 à 21.50.25

The database is configured but is not quite finished because I want to implement some methods (like CreatePost and GetPosts) but for that we first need a Post Model.

Models

Go ahead and create a new package models inside of the app package and create a post.go file. Inside of it we are going to have a Post struct with 4 fields.

Capture d’écran 2021-04-17 à 22.02.41

Since this is a REST API we are later going to be mapping our responses to JSON and sometimes the fields from our database might not correspond to our json fields or maybe we want to have flexibility to add new fields or remove fields.
For that, let's already create a JsonPost struct to our file.

Capture d’écran 2021-04-17 à 22.06.21

Last thing I want to add to this file is going to be useful in a few seconds. When we add a post to our database the ID is auto incremented, meaning we do not have to pass the ID when making a request to create a new post. So let's create a PostRequest struct for that.

Capture d’écran 2021-04-17 à 22.12.10

Back to our database in our db.go file I want to add 2 methods to my PostDB interface, GetPosts and CreatePost.

Note: I'll not be implementing all the REST verbs endpoints (trying to keep it short) just a simple GET and POST.

Capture d’écran 2021-04-17 à 22.17.10

In our schemas.go file I want to add a simple insertPostSchema for my CreatePost method.

Capture d’écran 2021-04-17 à 22.58.42

Let's create a methods.go file and write our methods.

Capture d’écran 2021-04-17 à 22.22.10

Http Handlers

Now that we have all of our methods to interact with our database, let's write our http handlers as we did before for the IndexHandler. In our handlers.go file let's start with the CreatePostHandler.

What we are going to do is initialize an empty PostRequest struct, the parse the request body that the user entered to that struct. To parse or map the request body I am going to write a helper func named simply parse that I will put in a helpers.go file.

Capture d’écran 2021-04-17 à 22.32.16

Capture d’écran 2021-04-17 à 22.34.00

Once again, this is a REST API so it is good practice to send a http status. We are going to send a lot of responses with an http status, so I will write another helper func for this in the helpers.go file that sends a JSON response with a status.

Capture d’écran 2021-04-17 à 22.38.57

Capture d’écran 2021-04-17 à 22.39.33

By the way, remember we had a JsonPost struct in case we want that flexibility? Let's add a last helper function (helpers.go) just to map data to that struct.

Capture d’écran 2021-04-17 à 22.45.26

Continuing with the CreatePostHandler, now that we have parsed the request body we need to use that data to create a new post and save it in our database.

Capture d’écran 2021-04-17 à 22.56.09

Now to finish the CreatePostHandler we just have to create a new route in our func initRoutes from our app.go file and call our handler.

Capture d’écran 2021-04-17 à 22.53.01

Run go run main.go again and let's test this on Insomnia.

Capture d’écran 2021-04-17 à 23.04.22

Seems to be working 👍🏻

Before moving on to tests let's implement GetPostHandler that should be simpler.

Capture d’écran 2021-04-17 à 23.14.36

Capture d’écran 2021-04-17 à 23.17.28

Relaunch the app
Capture d’écran 2021-04-17 à 23.18.28

Test with insomnia
Capture d’écran 2021-04-17 à 23.19.25

Perfect, this is working 🙂

Tests

It is also very good practice to test our code, that's why we need to try to add test coverage to our app. For that you can create a test package and add a test file for what you want to test, for example handler_test.go or db_test.go.

Capture d’écran 2021-04-17 à 23.23.39

I have actually made a video and wrote an article about implementing tests in Golang so you can can check those out 🙂

Conclusion

That's it! I am going to stop this article here, I know there is room for improvement but I wanted to keep this short and just talk about the foundation.

As always don't hesitate to give me feedback, the role of a developer today is to keep improving and that's what I want to do 🙂

Don't hesitate to check my Youtube Channel, you can also contact me on my twitter account and of course I will link the github repository for this article.

See you soon! 👋🏻

Top comments (15)

Collapse
 
euantorano profile image
Info Comment hidden by post author - thread only accessible via permalink
Euan T • Edited

Nice thorough guide, but it might be worth mentioning that the recommended practice is to use a custom http.Server with timeouts and such, as described here.

Another useful thing to perhaps mention is using the request context when running DB queries in order to handling timing them out and cancelling them. Contexts are also useful to pass context from middleware to handlers too :)

Collapse
 
lucasnevespereira profile image
Info Comment hidden by post author - thread only accessible via permalink
Lucas Neves Pereira

Thanks, I appreciate your feedback :)

Collapse
 
vladimirstepanov profile image
Info Comment hidden by post author - thread only accessible via permalink

Author's response about this situation:
"Hello there :) I actually delete it because some of the concepts were inspired by other content creators and didn't wanted to disrespect their work. Therefore, I decided to delete that article and rewrite it better."

We'll be wait ^_^

Collapse
 
deven96 profile image
Diretnan Domnan

Nice article!

Collapse
 
lucasnevespereira profile image
Lucas Neves Pereira

I appreciate it :)

Collapse
 
stremovsky profile image
Yuli

Hi,

You are missing a privacy section.

Privacy by design compliance

Privacy by design compliance is a challenging task for most startups. It is better to build it from the first steps of the product. Otherwise, you will have to add privacy to an already developed product. It is much more complicated and much more expensive.

You can build privacy by design on your own, or go with the open-source tools, for example with the Databunker project (databunker.org/). Databunker is a network-based, self-hosted, GDPR compliant, secure storage for personal data.

Self-promotion

I am an open-source security developer. I build and maintain the Databunker project.

Databunker will make your company or service privacy by design compliant and it is absolutely for free.

BR, Yuli

Collapse
 
lucasnevespereira profile image
Lucas Neves Pereira

Hello I appreciate the feedback! I will try to implement it 👍🏻 I’ll be updating this post with what I learn :)

Collapse
 
cteyton profile image
Cédric Teyton

Hi Lucas, FYI we've added your blog post on a GitHub project gathering content about best coding practices :)
github.com/promyze/best-coding-pra...

Collapse
 
tadeubernacchi profile image
Tadeu Bernacchi

Awesome, thanks for sharing =)

Collapse
 
lucasnevespereira profile image
Lucas Neves Pereira

You're Welcome :)

Collapse
 
vladimirstepanov profile image
Vladimir

Maaaaaaaaaaaaaan! Why do you delete this amazing article??(((

Collapse
 
dmuth profile image
Douglas Muth

Agreed--I came here from a link in an email, and I'm disappointed to see that the entire post was removed.

I think it would have been more appropriate to mark whatever parts of the post may have been in error with a note saying something to the effect, "It has been brought to my attention that this section may not be 100% accurate, and an update is pending"

Collapse
 
fuzzknob profile image
Info Comment hidden by post author - thread only accessible via permalink
Gagan Rai

Thanks for the article. There needs be more go lang articles in this site.

Collapse
 
pratiksinha profile image
Info Comment hidden by post author - thread only accessible via permalink
pratik-sinha

showing 404 :/

Collapse
 
matawed profile image
matawed

The same is true for me. I started reading it and wanted to continue after some time. Now it’s gone.

Some comments have been hidden by the post's author - find out more