All You Need to Build a Simple REST API With Golang — Part I
In this series of articles, I will try to cover all the concepts and details that you need to understand in order to build a simple, organized and intuitive rest API implementation using Golang.
In that first part, I will cover the basic architecture to handle the http requests an respond with some fake data.
First of all, some premises:
1 - I will assume that, if you came to this point, you already have Go installed and configured to run our examples. If not, there is plenty off good articles and tutorials that can guide you to accomplish that.
2 - Personally, I use the Visual Code Studio IDE and recommend it’s use with the Go extension, because it turns the development in golang much more easy and intuitive.
That said, let’s begin to work!
We will call our project as “go-rest”, so we will create a base directory with this name to receive our files.
The first step is to create our basic tree of directories. It will be based on the structure proposed by the “golang-standards” repository.
Lets begin with our main command, that will start our application. It needs to be created inside a “cmd” directory with the same name of the desired executable, so we need to create the following tree:
Inside the “cmd/go-rest” directory we will create a “main.go” file:
Pretty simple right? Everything needs to begin in some way. The attention points here are only the package name, that needs to be “main”, and the declaration of the “main” function that is mandatory to be the entry point of our application.
Before we continue, let’s initialize our go module. This needs to be done in order to let the “go mod” to keeps track of the dependencies of our module.
$go mod init
go: creating new go.mod: module github.com/marcosap/go-rest
Now we already may run our application, calling it from the base directory of the project using “go run”.
$ go run cmd/go-rest/main.go
2021/03/30 21:27:46 I'm go-rest, nice to meet you!
Now let’s create the http basic structure to listen to the requests. To do this we will create a package called “api”. Following the proposed directories structure, we will create it inside a “internal” directory. It tells Go that it’s a package intended to internal user and will not be available to be included by another projects or packages outside our go-rest project.
Now we have the following directories tree:
Inside the api directory, we need to create a “api.go” file. The first thing to do, is to indicate on wich package that file belongs. This is done by the “package” directive. Then let’s create a struct to contains our API implementation state, containing a router from the gorilla mux package and a function to create an instance of our API:
Since we include a external package, we need to get it. This can be done using the “go get” command in your terminal.
$ go get github.com/gorilla/mux
Now let’s create a method that starts our API, creating a listener ready to accept requests:
The “ListenAndServe” function of the http module is receiving the port that our API will listen and the router of our API. The use of the router will be explained later in this article. The error handling is something pretty recurrent in Go, and is always a good practice to handle errors accordingly. That habit will helps to prevent unnecessary headaches when your software grows.
With this basic structure, we can go back to our “main” function, instantiate a API, start it and do a basic test. This is how we do this in code:
Now, we can run our cmd again with:
$ go run cmd/go-rest/main.go
2021/04/07 18:13:13 I'm go-rest, nice to meet you!
2021/04/07 18:13:13 Starting the API
If none error is found, the API is listening on port 9000. You can test this in browser, curl, wget, postman or any software able to do http requests. But, at that point, any request to “http://localhost:9000” will respond with a “404 Not Found” since we don’t have any resource to serve with our api. Anyway, the test is util to make sure that our it is listening as intended.
Now, we need to define what will be a resource to our api. The idea is to create a interface that defines witch methods a resource needs to provide in order to works well with our API architecture. We will define it inside the api package. Let’s create a new file called “api_resource.go”:
That file defines:
- An ApiRoute struct containing the url of the resource, and a handler function that will be used to set the routes that will be served by theAPI.
- An interface called ApiResource that defines what any struct needs to provide in order to be used as a resource of the API. In this case a method that returns a array of ApiRoute’s.
Now that we have a complete definition of a ApiResource, we can define a method of the API in order to add resources to be served. The implementation can be done in the “api.go” file itself.
Note that the router that we initialized in the beginning, are being used now. The api will receive a resource, get the routes defined by it, and set it on the router. Simple and direct.
So, let’s add some resources. My lack of creativity, says that animals and cars are very good resources to use in our examples ; ) .
Lets create a package to our “animals” resource. The procedure is the same that we used to create the api package, create a directory inside the “internals” to hold our files:
Inside the animals directory, let’s create a animal.go file where we will define the structure that represents a animal to our API.
The focus of that article is the architecture of the API implementation, so lets be simple in terms of data.
Finally we can define anApiResource to serve our “/api/animals” route. This can be done in a separate “animals_resource.go” file, in order to maintain a good organization of our code:
The AnimalsResource is a empty struct at this point. In the second part of this articles series, we will add some members to integrate a database to our API. Note the the AnimalsResource implements the ApiResource interface that we have previously defined.
Now, lets implements the “getAllAnimals” handler. In this moment, we will add some mock data in order to serve our example. Remember that the main focus here is to understand the proposed architecture and organization of the proposed API implementation.
Pretty simple, we just get all our mock data, serialize it and, if everything goes well, write it using the ResponseWriter.
One last thing to finally test our first GET with some data. We need to add our AnimalsResource to the api. This will be done in our main function:
We just instantiate an AnimalsResource and pass it to the API using the “AddResource” method, before starts the API.
Finally, we can run our cmd to make the so expected test:
$go run cmd/go-rest/main.go
2021/04/09 18:43:41 I'm go-rest, nice to meet you!
2021/04/09 18:43:41 Starting the API
Now our API is up and ready to respond a GET to the “/api/animals” route. Let’s use curl to do the request:
$ curl http://localhost:9000/api/animals --silent | jq
[
{
"type": "dog",
"name": "Bob"
},
{
"type": "cat",
"name": "Felix"
}
]
It works, our mock data is being served by our API successfully!
In order to show the flexibility of our implementation, let’s add the cars resource, using the same steps that we used before to add the animals.
Create the directory:
Define the car data inside a “car.go” file:
Create a “CarsResource” to define the api handlers:
Now that we have our resource, lets add it to the API:
That’s it! Now lets run our sample application…
$ go run cmd/go-rest/main.go
2021/04/19 17:59:29 I'm go-rest, nice to meet you!
2021/04/19 17:59:29 Starting the API
and ask for our cars collection:
$ curl http://localhost:9000/api/cars --silent | jq
[
{
"model": "Tucker Torpedo",
"year": "1948"
},
{
"model": "Ford GT40",
"year": "1966"
}
]
Voilà! Looks like our API is very simple to extends.
As a bonus, lets add a new handler to the animals route, in order to get a single animal by it’s name. To do this, we just needs to define a new route in the “GetRoutes” method of the AnimalsResource and defines a new http handler to it. The important difference is that, this new handler will receive a url parameter with the name of the animal. The definition of parameters can be done puting the parameter name inside “{}” in the declared route:
Now, lets take a look to the implementation of the “getAnimalByName” handler:
The “name” parameter value can be obtained using the “mux.Vars()” function. It, provides a map with all parameters indexed by it’s names, very usefull and simple. Note that we use the same “json.Marshal” call to serialize our data. The json package is simply amazing to use IMHO, it’s the kind of thing that makes Go so productive. It’s good to note that the “json.Marshal” function uses the “`json:`" tags defined in the struct definitions to make the translation.
Let’s test our new handler:
$ curl http://localhost:9000/api/animals/Bob --silent | jq
{
"type": "dog",
"name": "Bob"
}$ curl http://localhost:9000/api/animals/Felix --silent | jq
{
"type": "cat",
"name": "Felix"
}
Everything working very well!
We will stop here for now, all the code created in the examples are available on github, I hope it can serve well for some of you! In the second part, we will learn how to integrate a database and define the other CRUD operations to this API implementation. Thanks for reading and until the part II.
UPDATE: The part II is available here: