All You Need To Build A Simple Rest API using Golang — Part II: Adding MongoDB
In the first part, we learn how to build the basic structure to serve some mock data with our API. Now is the time to integrate a database to make things more fun, and useful of course.
If you don’t read the first part yet, here it is:
All You Need to Build a simple RESTfull 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…
So, let’s begin!
MongoDB is a great NoSQL database and I think that it is perfect for our project because it is powerful, flexible and very easy to learn and start developing on top of it. Personally, I’ve been using it in all new projects I started recently, some of then are considerably huge, and MongoDB never disappointed me =).
Since the focus of the article is not the database, but its integration with the previously developed API implementation, we will not cover details about the MongoDB initial setup. My suggestion is to start a very simple container using docker, this is probably the easiest way to get a MongoDB up and running with the minimum that we need to start.
$ docker run --name mongodb -p 27017:27017 mongo
This will start a MongoDB whose data will be stored in a docker volume that may be removed, if you wants to keep your data safe, you can mount a local directory. All these details are very well documented in the MongoDB docker hub page.
Now we can return to our code. In order to maintain the separation of concerns, let’s create a new package to implements our data layer.
Inside it, let’s create a “database.go” file to implements the basic structure of our database implementation.
This is the absolutely minimum we need to establish a connection with our MongoDB instance. You may ask, why to create a wrapper around the MongoDB client? This will be clear later in the implementation. This package provide us a “NewDatabase” function, that creates the connection itself. Note that, using the context in that way, the “Connect” call will block until it is able to communicate with the MongoDB. If some error occur during this proccess, the “log.Fatalf” calling will end the application with a message indicating the error. The Database struct provide a “Disconnect” method, that will be used to ensure the client disconection at the end of execution of our API.
We can now test the connection already, lets simply call the “NewDatabase” function and set the disconnection to be executed at the end of the “main” function execution. This can be done using the “defer” directive:
Now, let’s test running the application.
$ go run cmd/go-rest/main.go
2021/04/24 11:48:00 I'm go-rest, nice to meet you!
2021/04/24 11:48:07 database.NewDatabase - connected!
2021/04/24 11:48:07 Starting the API
Great! We are now connected to MongoDB and ready to proceed.
Let’s begin with a create operation. First we need to define all will be needed to insert the data. We will create an abstract type, defined by a interface, that will be received by our database. Since in MongoDB all data is inserted in collections identified by its name, our “DatabaseEntity” type needs to provide a method to give its collection name.
Now we can create a method in our “Database” type, to provide a ‘mongo.Collection” given its name. This can be done in the “database.go” file.
Since this is the only place where de database name will be used, there is no problem to let it hardcoded here.
Now we can provide a method in the “Database” to insert a “DatabaseEntity”. For this, let’s create a separate “database_create.go” file.
Straight to the point, we just receive the entity and insert it into the database. Of course that this implementation, permits multiple insertions of the same data, but remember that the focus of this article is the proposed architecture, so we will maintain the simplicity to make the key concepts more clear.
Now we need to make the database available inside our api resources, begining with the “AnimalsResource”. To do this, we just add a database as a member of the struct. In order to implements a widely used pattern, we will provide a “NewAnimalsResource” too.
Now that this signature has changed, let’s modify our main function to use the new way to create a “AnimalsResource”.
The last thing to do before implements the create handler itself is to turn our “Animal” struct into a “DatabaseEntity”. To do it, we just need to implements the methods defined previously by the interface. So, lets add the “GetCollectionName” to it, instructing the “Database” that the collection will be called “animals”.
With all done, we can create the handler to create a new animal in the database.
This handler is pretty simple following the purpose of the article. The important thing to note here is the use of the database instance calling the Create method and passing the parsed animal to it.
Last but not least, we need to create the route to add animals. The create operation will map to a http POST.
Now we can test the create. Lets post some data using postman.
We can check if the data is successfully inserted using the mongodb shell or a gui client called MongoDB Compass like the bellow example
Great! We have the data being inserted into the database at the “animals” collection. Now let’s proceed re-implementing the RETRIEVE operation to get rid of those mock and fetch the real ones from MongoDB.
Let’s begin with the “getAllAnimals” handler. First we will need to provide database methods to retrieve data. So we will create a “database_retrieve.go” file that will will hold all retrieve implementation.
The important thing to note here, is that the “DatabaseEntity” needs to provide us a way to get a real instance of it’s abstract type. To have it, we will need to add a “New” method to the “DatabaseEntity” and implements it in all resources that will be used as an entity. It will be a very simple method that returns a new pointer to the entity struct instance.
The bellow sample shows the new signature of the “DatabaseEntity” interface and the implementation provided by the “Animal” struct.
Seems a little complicated, but this will help to simplify the API part of the implementation.
We added a single line to retrieve the data, a error handler and pass the data directly to the json serializer. That way we have isolated all the database logic successfully.
In the first part we provided a route to get an Animal by it’s name to. Let’s re-implement it to fetch real data too. Let’s follow the same logic to identify if we need to add something to our “DatabaseEntity”, implementing the database logic first. So, inside the “database_retrieve.go” file, lets create a nem method “RetriveOne”.
To have it working, we need that the “DatabaseEntity” gives us the information about witch field it will use as a filter to fetch a single result. This will be done by the “GetFilterOne” method as showed above. Of course that this implementation will get only the first occurrence of the data if we have 2 or more animals with the same name. In the real world, aka production, we will need a more refined “primary key” logic, but the main idea of the proposed implementation can serve us well.
Bellow we show the new signature of the “DatabaseEntity” interface, and their implementation to the “Animals” struct.
The “GetFilterOne” method returns just a map with the key/value pair that the database will use as the filter to it’s query.
Now we can get to the API handler implementation.
Mission accomplished! We get rid of all the mock data and have a very concise function handling only with the API matters.
I am not showing the test results to every step anymore, to don’t make the article to extensive, but is something that I recommend, in order to resolve the possible encountered problems as soon as possible.
Now let’s implement a route to update our animals info. Let’s suppose that you discover that Romeo the dog is indeed a llama, an update function will solve our problem. First, we need to create a database method to update an existing entity.
The boolean returned by the method indicates if the entity exists on the database. Now let’s take a look at the API handler implementation.
We just parse the body, fill the animal name with the value received in the url parameter a submit it to the database using the “Update” method and test the result to respond with the right http status code.
Now, we just need to add the new route to call our update handler responding to the PUT verb.
That’s it! You can already test the route sending a PUT request to the defined url.
Finally, lets add the delete handler. Starting with the database implementation.
here we use the same logic to respond to the caller if the entity exists in the database. The deletion is pretty direct, we just needs to provide the filter that we already have defined in the “GetFilterOne” method.
Now, the API handler.
We just need to create a animal with the provided name and submit it to the database “Delete” method.
The delete route will match with the DELETE verb. Here is our complete “ApiRoute” array at the end.
And that’ it, our animals resource is complete, you can do tests with all CRUD operations. Now you can reproduce all the work to the cars resource we have created in the first part of this series. You will see that it will be very simple, since all the database logic is done. Have fun =P.
Hope you liked the examples and could learn something with this proposed API architecture. We cover many details that involves many aspects of the development using Go an how to maintain a good organization of your Go code. Besides that, we try to pay attention to important concepts like the separation of concerns and the semantics of our variables, functions and types naming.
The sample code is available at github.
Thanks for your time!