Go-lang Gorilla API + MongoDB in 30 minutes
Published: 2022-07-24 23:32:22

Now hold your horses, before you start the thirty minute timer there is some house keeping we need to do first.One, we need to talk about No-SQL database family that MongoDB belongs to,two, we also need to touch on how to install MongoDB in our system (i am assuming you are using one of the Linux distros), then we will integrate this MongoDB database with a simple Gorilla-Mux API.

The code to this article can be found [here](https://github.com/myk4040okothogodo/MongoGorilla).

**No-SQL(Non-Relational Databases)**

These are document-oriented databases that have a different data storing format to SQL database such as MySQL or PostgreSQL.There are various flavors of non-relational databases based on their data model. Main types are document databases like MongoDB, key-value databases like Redis, wide-column databases like Apache and Cassandra and finally graph databases like Orient-DB, Dgraph.

No-SQL databases give developers the freedom to store huge amounts of unstructured data. This is due to the fact that No-SQL databases are less structured or confined in format thus allowing for greater flexibility and adaptability in the type of data stored.

A popular example of an application that uses a No-SQL database would be Facebook's Messenger app that uses an Apache HBase to stream data to Hadoop clusters. Apache HBase is a wide-column database modeled on by Google's Big Table.

**Characteristics of No-SQL(non-Relational) Databases:**

- Simple to integrate and use with apps for developers
- Allows for Horizontal scaling or Scaling out.
- Allows for Fast Querying
- Have Flexible Schema.

Dive deeper into this topic at this article [here](https://www.mongodb.com/nosql-explained).

**Installing MongoDB**

We first update our apt and install the MongoDB as shown below:

```

mykmyk@skynet:~$ sudo apt update

```
then;

```
mykmyk@skynet:~$ sudo apt-get install mongodb

```
Now, to confirm if your MongoBD database has installed 
correctly, check that by doing the following:

```
mykmyk@skynet:~$ mongo
MongoDB shell version v3.6.3
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.3
Server has startup warnings: 
2022-07-23T22:58:19.824+0300 I STORAGE  [initandlisten] 
2022-07-23T22:58:19.824+0300 I STORAGE  [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2022-07-23T22:58:19.824+0300 I STORAGE  [initandlisten] **          See http://dochub.mongodb.org/core/prodnotes-filesystem
2022-07-23T22:58:30.835+0300 I CONTROL  [initandlisten] 
2022-07-23T22:58:30.835+0300 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2022-07-23T22:58:30.835+0300 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2022-07-23T22:58:30.835+0300 I CONTROL  [initandlisten] 
>
```

If you ran into errors while you were installing MongoDB, i refer you to this well written [article](https://linuxhint.com/install_mongodb_linuxmint/), it should guide sufficiently enough to get the database safely installed on your system.

To check the databases that are avaible in our MongoDB DMS:

```
> show databases;
admin   0.000GB
appdb   0.000GB
config  0.000GB
local   0.000GB
test    0.000GB

```

The above command "show databases" lists all available databases , by default,admin,test, and local are the three that should be available before you create any new database;
We are going to create a database called, "booksdb", so while still in your MongoDB shell:

```
> use booksdb;
switched to db booksdb

```

The command "use booksdb" above switches from the current database to the booksdb database that we just created. Its interesting to note that the "booksdb"  wont show up if we query all the databases available using the "show databases"  command. Go ahead and try it.

 ```
> use booksdb;
switched to db booksdb
> show databases;
admin   0.000GB
appdb   0.000GB
config  0.000GB
local   0.000GB
test    0.000GB
```
The above phenomenon results from the internal implementation  of MongoDB,a database is only created in MongoDB when actual initial data is inserted into it , so lets go ahead and do just that.

still on your MongoDB shell type in:

```
> db.books.insertOne({_id:5, title: 'A Song Of Ice and Fire',authors:['George R.R Martin','Phyllis Eisenstein'],publishdate:'August 1,1996',characters:['Jon Snow','Daenerys Targaryen','Eddard Star','Sansa Stark','Jammie Lannister','Rob Stark','Cerser Lannister'],publisher:{name:'Bantam Books',country:'United States',website:'www.randomhousebooks.com'}})
{ "acknowledged" : true, "insertedId" : 5 }

```
The json we just inserted has an ID called _id, we could either provide that id while inserting our books object or we could leave it to MongoDB to insert it for us. Lets try and insert a book without specifying an id below and see what MongoDB will do:

```
> db.books.insertOne({title: 'A Feast Of Crows',authors:['George R.R Martin'],publishdate:'August 1,1996',characters:['Jon Snow','Daenerys Targaryen','Eddard Star','Sansa Stark','Jammie Lannister','Rob Stark','Cerser Lannister'],publisher:{name:'Bantam Books',country:'United States',website:'www.randomhousebooks.com'}})
{
	"acknowledged" : true,
	"insertedId" : ObjectId("62dda3f1457193537fd4d245")
}
> 
```
As you can see above in the insert aknowledgment JSON response, insertId value has now changed to a long hash value that is generated internally by MongoDB, in our case it is "62dda3f1457193537fd4d245"

To search for books we use the find() keyword as shown below:

```
> db.books.find()
{ "_id" : 5, "title" : "A Song Of Ice and Fire", "authors" : [ "George R.R Martin", "Phyllis Eisenstein" ], "publishdate" : "August 1,1996", "characters" : [ "Jon Snow", "Daenerys Targaryen", "Eddard Star", "Sansa Stark", "Jammie Lannister", "Rob Stark", "Cerser Lannister" ], "publisher" : { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" } }
{ "_id" : ObjectId("62dda3f1457193537fd4d245"), "title" : "A Feast Of Crows", "authors" : [ "George R.R Martin" ], "publishdate" : "August 1,1996", "characters" : [ "Jon Snow", "Daenerys Targaryen", "Eddard Star", "Sansa Stark", "Jammie Lannister", "Rob Stark", "Cerser Lannister" ], "publisher" : { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" } }
> 

```
The find() without any supplied arguments returns all the 
documents in our "booksdb" collection. To return a single document we use the findOne(), if empty it returns latest document i.e:
```
> db.books.findOne()
{
	"_id" : 5,
	"title" : "A Song Of Ice and Fire",
	"authors" : [
		"George R.R Martin",
		"Phyllis Eisenstein"
	],
	"publishdate" : "August 1,1996",
	"characters" : [
		"Jon Snow",
		"Daenerys Targaryen",
		"Eddard Star",
		"Sansa Stark",
		"Jammie Lannister",
		"Rob Stark",
		"Cerser Lannister"
	],
	"publisher" : {
		"name" : "Bantam Books",
		"country" : "United States",
		"website" : "www.randomhousebooks.com"
	}
}
> 
```
We can supply filtering parameters to our find() collection queries as shown below:

```
> db.books.find({title:{$eq: "A Feast Of Crows"}})
{ "_id" : ObjectId("62dda3f1457193537fd4d245"), "title" : "A Feast Of Crows", "authors" : [ "George R.R Martin" ], "publishdate" : "August 1,1996", "characters" : [ "Jon Snow", "Daenerys Targaryen", "Eddard Star", "Sansa Stark", "Jammie Lannister", "Rob Stark", "Cerser Lannister" ], "publisher" : { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" } }

```
We can delete a document from a given collection using the deleteOne and deleteMany functions:

```
> db.books.deleteOne({"_id": ObjectId("62dda3f1457193537fd4d245")})
{ "acknowledged" : true, "deletedCount" : 1 }
```

Next stage we concentrate on building up our very simple GorillaMux API:

**Gorilla-Mux API**

First things first,to allow our API to talk to the MongoDB database we will be using a database driver called mgo.

**mgo- A Rich MongoDB driver for Go**

This is MongoDB driver for go-lang that enables developers to build applications like in our case and have that API interface directly with the MongoDB database without having to go through the Mongo interactive shell. This diver acts as a wrapper around the MongoDB API.

Check out the documentation on this driver [here](https://labix.org/mgo), for a deeper understanding on its implementation and application.

mgo installation:

First lets create a folder for our application, create a folder called MongoGorilla:

```
mykmyk@skynet:~/go/src/github.com/myk4040okothogodo/GoMongo$ mkdir MongoGorilla

```
then run go mod init, that create go.mod file to track the dependencies that we will use to build our API application like mgo driver above:

```
mykmyk@skynet:~/go/src/github.com/myk4040okothogodo/MongoGorilla$ go mod init
go: creating new go.mod: module github.com/myk4040okothogodo/MongoGorilla
mykmyk@skynet:~/go/src/github.com/myk4040okothogodo/MongoGorilla$ ll
total 12
drwxrwxr-x  2 mykmyk mykmyk 4096 Jul 23 23:24 ./
drwxrwxr-x 20 mykmyk mykmyk 4096 Jul 23 23:09 ../
-rw-rw-r--  1 mykmyk mykmyk   58 Jul 23 23:24 go.mod
```
Now, let install our driver "mgo" as shown below:

```
mykmyk@skynet:~/go/src/github.com/myk4040okothogodo/MongoGorilla$ go get gopkg.in/mgo.v2
go: downloading gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
go: added gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
```
**Gorilla-Mux**

Next we should add the gorilla/mux package , that will help use implement a very simple router in our API, to help match incoming request to respective handlers.
Dive deeper into the package and how it implements its dispatching features [here](https://github.com/gorilla/mux).

To install the package and have it accessible in our project, while in our project root folder that houses the go.mod file, type in the following:

```
mykmyk@skynet:~/go/src/github.com/myk4040okothogodo/MongoGorilla$ go get -u github.com/gorilla/mux
go: downloading github.com/gorilla/mux v1.8.0
go: added github.com/gorilla/mux v1.8.0
mykmyk@skynet:~/go/src/github.com/myk4040okothogodo/MongoGorilla$ 

```



**API Building**

Now lets write a  simple program that talks to the MongoDB database using our "mgo" driver and inserts a book " A song of Ice and Fire" book record.

Create a file called "main.go" open it and add the following code shown below:

```
 1 package main                                                                                                                                                        
  2 
  3 import (
  4   "os"
  5   "os/signal"
  6   "context"
  7   "time"
  8   "net/http"
  9   "io/ioutil"
 10   "encoding/json"
 11   "github.com/hashicorp/go-hclog"
 12   "log"
 13   "github.com/gorilla/mux"
 14   mgo "gopkg.in/mgo.v2"
 15   "gopkg.in/mgo.v2/bson"
 16 
 17 )
 18 
 19 
 20 // The struct below holds our database session information
 21 type DBSession struct {
 22     session      *mgo.Session
 23     collection   *mgo.Collection                                                                                                                                    
 24 }                                                                                                                                                                   
 25                                                                                                                                                                     
 26                                                                                                                                                                     
 27 //Book struct holds book data                                                                                                                                       
 28 type Book struct {                                                                                                                                                  
 29     ID            bson.ObjectId    `json: "id"          bson:"_id,omitempty"`                                                                                       
 30     Title         string           `json: "title"       bson:"title"`                                                                                               
 31     Authors       []string         `json: "authors"     bson: "authors"`                                                                                            
 32     Genre         []string         `json: "genre"       bson: "genre"`                                                                                              
 33     PublishDate   string           `json: "publishdate" bson: "publishdate"`                                                                                        
 34     Characters    []string         `json: "characters"  bson: "characters"`                                                                                         
 35     Publisher     Publisher        `json: "publisher"   bson:"publisher"`                                                                                           
 36 }                                                                                                                                                                   
 37                                                                                                                                                                     
 38  
   39 // Publisher is nested in Movie
 40 type Publisher struct {
 41     Name     string   `json: "budget"    bson:"name"`
 42     Country  string   `json: "country"   bson:"country"`
 43     website  string   `json: "website"   bson:"website"`
 44 }
 45 
 46 
 47 // GetBook fetches a book with a given ID
 48 func (db *DBSession) GetBook (w http.ResponseWriter, r *http.Request){
 49     vars := mux.Vars(r)
 50 
 51     w.WriteHeader(http.StatusOK)
 52     var book Book
 53     err := db.collection.Find(bson.M{"_id": bson.ObjectIdHex(vars["id"])}).One(&book)
 54     if err != nil {
 55         w.Write([]byte(err.Error()))
 56     } else {
 57         w.Header().Set("Content-Type", "application/json")
 58         response, _ := json.Marshal(book)
 59         w.Write(response)
 60     }
 61 }
 62 
 63 
  64 //PostBook adds a new book to our MongoDB collection
 65 func (db *DBSession) PostBook (w http.ResponseWriter, r *http.Request){
 66     var book Book
 67     postBody, _ := ioutil.ReadAll(r.Body)
 68     json.Unmarshal(postBody, &book)
 69 
 70     //Create a Hash ID to insert
 71     book.ID = bson.NewObjectId()
 72     err := db.collection.Insert(book)
 73     if err != nil {
 74         w.Write([]byte(err.Error()))
 75     } else {
 76         w.Header().Set("Content-Type","application/json")
 77         response, _ := json.Marshal(book)
 78         w.Write(response)
 79     }
 80 }
 81 
 82 
 83 
 84 
 85 //UpdateBook modifies the data of an existing book resource
 86 func (db *DBSession) UpdateBook(w http.ResponseWriter, r *http.Request){
 87     vars := mux.Vars(r)
 88     var book Book
 89 
 90     putBody, _ := ioutil.ReadAll(r.Body)
 91     json.Unmarshal(putBody, &book)
 92     err := db.collection.Update(bson.M{"_id":                                                                                                                       
 93         bson.ObjectIdHex(vars["id"])}, bson.M{"$set": &book})
 94 
 95     if err != nil {
 96         w.WriteHeader(http.StatusOK)
 97         w.Write([]byte(err.Error()))
 98     } else {
 99         w.Header().Set("Content-Type","text")
100         w.Write([]byte("Update succesfully"))
101     }
102 }
103 
104 
105 //DeleteBook removes the data from the db                                                                                                                           
106 func (db *DBSession) DeleteBook (w http.ResponseWriter, r *http.Request){
107      vars := mux.Vars(r)
108      err := db.collection.Remove(bson.M{"_id":
109          bson.ObjectIdHex(vars["id"])})
110      if err != nil {
111          w.WriteHeader(http.StatusOK)
112          w.Write([]byte(err.Error()))
113      } else {
114          w.Header().Set("Content-Type", "text")
115          w.Write([]byte("Delete Succesfully"))
116      }
117 }
118 
119 func main() {
120     l := hclog.Default()
121     session, err := mgo.Dial("127.0.0.1")
122     c := session.DB("booksdb").C("books")
123     db := &DBSession{session: session, collection:c}
124     addr := "127.0.0.1:8000"
125     if err != nil {
126         panic(err)
127     }
128     defer session.Close()
129     
130     //logger := log.New(os.Stdout, "", log.Ldate | log.Ltime)
131     // Create a new router
132     r := mux.NewRouter()
133 
134     //Attach an elegant path with handler
135     r.HandleFunc("/api/books/{id:[a-zA-Z0-9]*}", db.GetBook).Methods("GET")
136     r.HandleFunc("/api/books", db.PostBook).Methods("POST")
137     r.HandleFunc("/api/books/{id:[a-zA-Z0-9]*}", db.UpdateBook).Methods("PUT")
138     r.HandleFunc("/api/books/{id:[a-zA-Z0-9]*}", db.DeleteBook).Methods("DELETE")
139 
140     srv := &http.Server{
141         Handler:       r,
142         Addr:          addr,
143         ErrorLog:      l.StandardLogger(&hclog.StandardLoggerOptions{}),
144         IdleTimeout:   time.Minute,
145         WriteTimeout:  15 * time.Second,
146         ReadTimeout:   15 * time.Second,
147     }                                                                                                                                                               
148 
149     //start the server
150     go func() {
151       l.Info("Starting server on port 8000 ")
152       err := srv.ListenAndServe()
153       if err != nil {
154           l.Error("Error starting the server :", "error", err)
155           os.Exit(1)
156       }        
157     }()
158 
159 
160     //Trap sigterm or interupt and gracefully shutdown the server
161     ch := make(chan os.Signal, 1)
162     signal.Notify(ch, os.Interrupt)
163     signal.Notify(ch, os.Kill)
164     
165     //Block untill a signal is received
166     sig := <- ch
167     log.Println("Got signal :", sig)
168 
169 
170     //gracefully shutdown the server, waiting max 30 for current operations to complete
171     ctx, _ := context.WithTimeout(context.Background(), 30 * time.Second)
172     srv.Shutdown(ctx)
173 
174 }                                                                                                                                                         
```

Remember to run the command "go mod tidy", to have all the direct and indirect dependecies brought in.
 
In the above code we are first creating two structs , DBSession and Book struct;

DBSession structure will be used to hold our database session and collection properties of the mgo driver. Book structure will hold our book data, also note that the Book struct has a nested Publisher struct.

Next we define a receiver Pointer Method for our Book struct  that we intend to have implement Handler interface.In this case we first define the method GetBook, that fetches a book from the database using an id that has been supplied as part of the request.It then returns this method as a response. We then  define the second method, called PostBook that does just what its title suggests, it takes a book sent in as part of the Request body and then attempts to insert this book into our database. We the created another handler method called UpdateBook, that takes the updated values of a book that already exists in our database and tries to alter one of its values. Our Final Handler is the DeleteBook Method that is used the data pertaining to a book that we have stored in our database. We identify this particular book using an id that is sent in as part of the request. 

We then create our main function, in this we instantiate a session with our database that we will use for all our subsequent queries and updates, we then create a  new router using the NewRouter() method of gorilla/mux, we then attach our handlers to this router to enable dispatching of the various request to their subsequent handlers. We then launch our server as a go-routine , we then create a channel to listen and respond to server terminating signals.

To add a new book to our database and to test our POST endpoint lets make a post request to our API, open your terminal from any location and make the following curl request:

```
mykmyk@skynet:~/go/src/github.com/myk4040okothogodo/MongoGorilla$ curl -X POST \
> http://localhost:8000/api/books \
> -H 'cache-control: no-cache' \
> -H 'content-type: application/json' \
> -H 'postman-token: 6ef9507e-65b3-c3dd-4748-3a2a3e055c9c' \
> -d '{"title" :"A Dance with Dragons","authors":[ "George R.R Martin", "Phyllis Eisenstein"],"publishdate":"August 6,1998","characters":[ "Jon Snow", "Daenerys Targaryen", "Eddard Star", "Sansa Stark", "Jammie Lannister", "Rob Stark", "Cerser Lannister" ],"publisher": { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" }}'

```

You will receive a reply from the API server that will look like:

```
{"ID":"62ddc5e89c99ea1313ae2568","Title":"A Dance with Dragons","Authors":["George R.R Martin","Phyllis Eisenstein"],"Genre":null,"PublishDate":"August 6,1998","Characters":["Jon Snow","Daenerys Targaryen","Eddard Star","Sansa Stark","Jammie Lannister","Rob Stark","Cerser Lannister"],"publisher": { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" }}
```

Go to your database and confirm that there us a new entry, whose id has been auto-generated by MongoDB for us, it should look like:

```
{ "_id" : ObjectId("62ddc5e87071de3d88044140"), "id" : ObjectId("62ddc5e89c99ea1313ae2568"), "title" : "A Dance with Dragons", "authors" : [ "George R.R Martin", "Phyllis Eisenstein" ], "genre" : [ ], "publishdate" : "August 6,1998", "characters" : [ "Jon Snow", "Daenerys Targaryen", "Eddard Star", "Sansa Stark", "Jammie Lannister", "Rob Stark", "Cerser Lannister" ], "publisher" : { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" }}

```

Lets test our GetBook endpoint, we are go supply the id that was returned when we added our book using the PostBook endpoint above, i.e in my case "62ddc5e89c99ea1313ae2568":

```
mykmyk@skynet:~/go/src/github.com/myk4040okothogodo/MongoGorilla$ curl -X GET \
> http://localhost:8000/api/books/62ddc5e89c99ea1313ae2568 \
> -H 'cache-control: no-cache' \
> -H 'postman-token: 00282916-e7f8-5977-ea34-d8f89aeb43e2'
```

It should return:
```
{"ID":"62ddc5e89c99ea1313ae2568","Title":"A Dance with Dragons","Authors":["George R.R Martin","Phyllis Eisenstein"],"Genre":[],"PublishDate":"August 6,1998","Characters":["Jon Snow","Daenerys Targaryen","Eddard Star","Sansa Stark","Jammie Lannister","Rob Stark","Cerser Lannister"],"publisher" : { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" }}
```
To test the delete endpoint:
```
curl -X DELETE http://localhost:8000/api/books/62ddc5e87071de3d88044140
```
it should return:
```
Delete Successfully
```
Now if we check our database using the find(), command we find that, the specific book "A Dance with Dragons" has been removed i.e:

```
> db.books.find()
{ "_id" : 5, "title" : "A Song Of Ice and Fire", "authors" : [ "George R.R Martin", "Phyllis Eisenstein" ], "publishdate" : "August 1,1996", "characters" : [ "Jon Snow", "Daenerys Targaryen", "Eddard Star", "Sansa Stark", "Jammie Lannister", "Rob Stark", "Cerser Lannister" ], "publisher" : { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" } }
{ "_id" : ObjectId("62dda3f1457193537fd4d245"), "title" : "A Feast Of Crows", "authors" : [ "George R.R Martin" ], "publishdate" : "August 1,1996", "characters" : [ "Jon Snow", "Daenerys Targaryen", "Eddard Star", "Sansa Stark", "Jammie Lannister", "Rob Stark", "Cerser Lannister" ], "publisher" : { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" } }

```

To test the Put endpoint, that we will use to alter specific values of of book data:

I want to change the publisher to the book "A Feast of Crows" from "Bantam Books" to "Voyager" and i want to remove one of the authors "Phyllis Eisenstein"

the books currently looks like:
```
{ "_id" : 62dda3f1457193537fd4d245, "title" : "A Feast Of Crows", "authors" : [ "George R.R Martin", "Phyllis Eisenstein" ], "publishdate" : "August 1,1996", "characters" : [ "Jon Snow", "Daenerys Targaryen", "Eddard Star", "Sansa Stark", "Jammie Lannister", "Rob Stark", "Cerser Lannister" ], "publisher" : { "name" : "Bantam Books", "country" : "United States", "website" : "www.randomhousebooks.com" } }
```
Can you figure out how to alter the data, hint:

```
 curl -X PUT http://localhost:8000/api/books/.................
```
If you get stuck put it down in  the comments and i will be sure to help.


**Conclusion**
In this article we have mainly looked at MongoDB, how to install and add data to it using its interactive shell, then we talked about building a simple gorilla-mux api and having it interface with our database to store, delete and alter the details of books send using HTTP request methods like PUT, POST, GET and DELETE.

Goodbye,see you soon.