Developing A RESTful API With Golang And A MongoDB NoSQL Database

Creating a New Go Project with the MongoDB Dependencies

There are many ways to create a REST API with Golang. We’re going to be making use of a popular multiplexer that I wrote about in a previous tutorial titled, Create a Simple RESTful API with Golang.

Create a new directory within your $GOPATH and add a main.go file. Before we start adding code, we need to obtain our dependencies. From the command line, execute the following:

go get github.com/gorilla/mux
go get go.mongodb.org/mongo-driver/mongo

The above commands will get our multiplexer as well as the MongoDB SDK for Golang. With the dependencies available, open the main.go file and include the following boilerplate code:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"
	"github.com/gorilla/mux"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
)

var client *mongo.Client

type Person struct {
	ID        primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
	Firstname string             `json:"firstname,omitempty" bson:"firstname,omitempty"`
	Lastname  string             `json:"lastname,omitempty" bson:"lastname,omitempty"`
}

func CreatePersonEndpoint(response http.ResponseWriter, request *http.Request) {}
func GetPeopleEndpoint(response http.ResponseWriter, request *http.Request) { }
func GetPersonEndpoint(response http.ResponseWriter, request *http.Request) { }

func main() {
	fmt.Println("Starting the application...")
	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
	client, _ = mongo.Connect(ctx, clientOptions)
	router := mux.NewRouter()
	router.HandleFunc("/person", CreatePersonEndpoint).Methods("POST")
	router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
	router.HandleFunc("/person/{id}", GetPersonEndpoint).Methods("GET")
	http.ListenAndServe(":12345", router)
}

So what is happening in the above code? Well, we’re not actually doing any MongoDB logic, but we are configuring our API and connecting to the database.

In our example, the Person data structure will be the data that we wish to work with. We have both JSON and BSON annotations so that we can work with MongoDB BSON data and receive or respond with JSON data. In the main function we are connecting to an instance of MongoDB, which in my scenario is on my local computer, and configuring our API routes.

While we could create a full CRUD API, we’re just going to work with three endpoints. You could easily expand upon this to do updates and deletes.

With the boilerplate code out of the way, we can focus on each of our endpoint functions.

Designing API Endpoints for HTTP Interaction

Assuming that we’re working with a fresh instance of MongoDB, the first thing to do might be to create data. For this reason we’re going to work on the CreatePersonEndpoint to receive client data.

func CreatePersonEndpoint(response http.ResponseWriter, request *http.Request) {
	response.Header().Set("content-type", "application/json")
	var person Person
	_ = json.NewDecoder(request.Body).Decode(&person)
	collection := client.Database("thepolyglotdeveloper").Collection("people")
	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
	result, _ := collection.InsertOne(ctx, person)
	json.NewEncoder(response).Encode(result)
}

In the above code we are setting the response type for JSON because most web applications can easily work with JSON data. In the request there will be a JSON payload which we are decoding into a native data structure based on the annotations.

Now that we have data to work with, we connect to a particular database within our instance and open a particular collection. With a connection to that collection we can insert our native data structure data and return the result which would be an object id.

Since we know the id, we can work towards obtaining that particular document from the database.

func GetPersonEndpoint(response http.ResponseWriter, request *http.Request) {
	response.Header().Set("content-type", "application/json")
	params := mux.Vars(request)
	id, _ := primitive.ObjectIDFromHex(params["id"])
	var person Person
	collection := client.Database("thepolyglotdeveloper").Collection("people")
	ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
	err := collection.FindOne(ctx, Person{ID: id}).Decode(&person)
	if err != nil {
		response.WriteHeader(http.StatusInternalServerError)
		response.Write([]byte(`{ "message": "` + err.Error() + `" }`))
		return
	}
	json.NewEncoder(response).Encode(person)
}

With the GetPersonEndpoint we are passing an id as the route parameter and converting it to an object id. After getting a collection to work with we can make use of the FindOne function and a filter based on our id. This single result can then be decoded to a Person object.

As long as there were no errors, we can return the person which should include an id , a firstname , and a lastname property.

This leads us to the most complicated of our three endpoints.

func GetPeopleEndpoint(response http.ResponseWriter, request *http.Request) {
	response.Header().Set("content-type", "application/json")
	var people []Person
	collection := client.Database("thepolyglotdeveloper").Collection("people")
	ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
	cursor, err := collection.Find(ctx, bson.M{})
	if err != nil {
		response.WriteHeader(http.StatusInternalServerError)
		response.Write([]byte(`{ "message": "` + err.Error() + `" }`))
		return
	}
	defer cursor.Close(ctx)
	for cursor.Next(ctx) {
		var person Person
		cursor.Decode(&person)
		people = append(people, person)
	}
	if err := cursor.Err(); err != nil {
		response.WriteHeader(http.StatusInternalServerError)
		response.Write([]byte(`{ "message": "` + err.Error() + `" }`))
		return
	}
	json.NewEncoder(response).Encode(people)
}

The GetPeopleEndpoint should return all documents within our collection. This means we have to work with cursors in MongoDB similar to how you might work with cursors in an RDBMS. Using a Find with no filter criteria we can loop through our cursor, decoding each iteration and adding it to a slice of the Person data type.

Provided there were no errors along the way, we can encode the slice and return it as JSON to the client. If we wanted to, we could add filter criteria so that only specific documents are returned rather than everything. The filter criteria would include document properties and the anticipated values.

Refer: https://www.thepolyglotdeveloper.com/2019/02/developing-restful-api-golang-mongodb-nosql-database/