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.
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.
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.
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.