Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Developing A RESTful API With Golang And A MongoDB NoSQL Database

TwitterFacebookRedditLinkedInHacker News

This tutorial was updated on June 27, 2019 to reflect the latest versions of the technologies mentioned.

If you’ve been following along, you’re probably familiar with my love of Node.js and the Go programming language. Over the past few weeks I’ve been writing a lot about API development with MongoDB and Node.js, but did you know that MongoDB also has an official SDK for Golang? As of now the SDK is in beta, but at least it exists and is progressing.

The good news is that it isn’t difficult to develop with the Go SDK for MongoDB and you can accomplish quite a bit with it.

In this tutorial we’re going to take a look at building a simple REST API that leverages the Go SDK for creating data and querying in a MongoDB NoSQL database.

Before going forward, we’re going to assume that you already have an instance of MongoDB configured and Golang is installed and configured as well. If you need help, I wrote a simple tutorial for deploying MongoDB with Docker that you can check out. It is a great way to get up and running quickly.

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.

Conclusion

You just saw how to create a simple REST API with Golang and MongoDB. This is a step up from my basic tutorial titled, Create a Simple RESTful API with Golang.

Being that the MongoDB SDK for Go is in beta, the official documentation is pretty terrible. I found that much of it didn’t work out of the box and it advised things that might not be the best approach. While I did my best to fill in the gap, there may be better ways to accomplish things. I’m open to hearing these better ways in the comments.

A video version of this tutorial can be found below.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.