Tutorial: Getting started with MongoDB on Azure using Go
Tags: azure, go, mongodb, showdev
Published: 2020-04-03 13:55:08

As a [Go](https://golang.org/) enthusiast, it's great to see first class support for for MongoDB in the form of an [official MongoDB Go driver](https://github.com/mongodb/mongo-go-driver). We will learn how to use it by building a simple REST API for a good old CRUD-style app!

In this blog, will cover the following topics:

- Walkthrough the application: CRUD operations
- Setup Azure Cosmos DB for MongoDB API
- Setup Azure App service and deploy the application to the cloud
- Take the REST API for a spin

To follow along, you're free to use a MongoDB cluster of your choice ([Docker is probably the fastest/easiest option](https://hub.docker.com/_/mongo)). I will be using [Azure Cosmos DB](https://docs.microsoft.com/azure/cosmos-db/?WT.mc_id=devto-blog-abhishgu), which is Microsoft's globally distributed, multi-model database service that supports document, key-value, graph, and columnar data models. It implements the wire protocol for MongoDB making if possible for any MongoDB client driver (including the Go driver) that understands this protocol version to natively connect to Cosmos DB.

> You can Try [Azure Cosmos DB for Free](https://azure.microsoft.com/try/cosmosdb/?WT.mc_id=devto-blog-abhishgu) (for a limited time) without an Azure subscription, or use the [Azure Cosmos DB free tier](https://docs.microsoft.com/azure/cosmos-db/optimize-dev-test?WT.mc_id=devto-blog-abhishgu#azure-cosmos-db-free-tier) to get an account with the first 400 RU/s and 5 GB of storage free.

The application (API) will be deployed to [Azure App Service](https://docs.microsoft.com/azure/app-service/?WT.mc_id=devto-blog-abhishgu). It enables you to build and host web apps, mobile back ends, and RESTful APIs in the programming language of your choice without managing infrastructure. [You can use it on Linux](https://docs.microsoft.com/azure/app-service/containers/app-service-linux-intro?WT.mc_id=devto-blog-abhishgu) to host web apps natively on Linux for supported application stacks with support for built-in Docker images for [multiple languages such as Node.js, Java, Python](https://docs.microsoft.com/azure/app-service/containers/app-service-linux-intro?WT.mc_id=devto-blog-abhishgu#languages) etc. 

> Although we have a Go app in this example, we can still host it on App Service since it supports custom Docker images as well!

## Overview

The application is a simple one that exposes a REST API to create, read, update and delete developer profiles with a GitHub ID, blog and skills.

As usual, the code is [available on GitHub](https://github.com/abhirockzz/mongodb-go-app), but let's walk through it real quick!

![](https://media.giphy.com/media/nGMnDqebzDcfm/giphy.gif)

Here is the code layout:

```
.
├── Dockerfile
├── api
│   ├── crud.go
│   └── crud_test.go
├── go.mod
├── go.sum
├── main.go
├── model
│   └── developer.go
└── test.sh
```

The main CRUD operations are in `crud.go` file within the `api` package. It contains implementation for create, read, update and delete operations. 

The `Create` function gets a handle to the MongoDB collection and marshals the request body (JSON payload) into a struct (`model.Developer`). The struct is then inserted using the [`InsertOne` function](https://godoc.org/go.mongodb.org/mongo-driver/mongo#Collection.InsertOne), the error is handled (not shown above) and an `HTTP 201` is sent back in case of success.


```
coll := a.Connection.Database(a.DBName).Collection(a.CollectionName)
...
var dev model.Developer
json.NewDecoder(req.Body).Decode(&dev)
...
res, err := coll.InsertOne(ctx, dev)
...
rw.WriteHeader(http.StatusCreated)
```

Here is how the read operation works:

```
vars := mux.Vars(req)
githubID := vars["github"]
...
coll := a.Connection.Database(a.DBName).Collection(a.CollectionName)
r := coll.FindOne(context.Background(), bson.M{githubIDAttribute: githubID})
...
var p model.Developer
r.Decode(&p)
json.NewEncoder(rw).Encode(&p)
```

[`FindOne` function](https://godoc.org/go.mongodb.org/mongo-driver/mongo#Collection.FindOne) is used with a filter criteria of `github_id` which is the partition key for the collection `bson.M{githubIDAttribute: githubID}`. If found, the result is converted to the struct and returned to the caller.

Fetching all developer profiles is similar

```
r, err := coll.Find(context.Background(), bson.D{})
...
devs := []model.Developer{}
err = r.All(context.Background(), &devs)
...
```

[`Find`](https://godoc.org/go.mongodb.org/mongo-driver/mongo#Collection.Find) uses an empty bson document as a filter in order to list all the documents in the collection and the result is sent back to the caller in the form of a JSON array

[`FindOneAndReplace`](https://godoc.org/go.mongodb.org/mongo-driver/mongo#Collection.FindOneAndReplace) is used to update the a specific record. `github_id` is used as the filter criteria and the passed in JSON payload is the updated record.

```
var dev model.Developer
json.NewDecoder(req.Body).Decode(&dev)
...
r := coll.FindOneAndReplace(context.Background(), bson.M{githubIDAttribute: githubID}, &dev)
...
rw.WriteHeader(http.StatusNoContent)
```

Delete is accomplished using [`FindOneAndDelete`](https://godoc.org/go.mongodb.org/mongo-driver/mongo#Collection.FindOneAndDelete) which accepts the `github_id` as the filter criteria for the record to be deleted 

```
vars := mux.Vars(req)
githubID := vars["github"]
...
r := coll.FindOneAndDelete(context.Background(), bson.M{githubIDAttribute: githubID})
...
rw.WriteHeader(http.StatusNoContent)
```

Everything is tied together in `main.go`. It associates the CRUD operation handlers to HTTP routes, starts the server and also sets a graceful exit mechanism

```
.....
func main() {
	r := mux.NewRouter() 
        r.Methods(http.MethodPost).Path("/developers")
                                  .HandlerFunc(crudAPI.Create)
	r.Methods(http.MethodGet).Path("/developers/{github}")
                                 .HandlerFunc(crudAPI.Read)
        r.Methods(http.MethodGet).Path("/developers")
                                 .HandlerFunc(crudAPI.ReadAll)
        r.Methods(http.MethodPut).Path("/developers")
                                 .HandlerFunc(crudAPI.Update)
        r.Methods(http.MethodDelete).Path("/developers/{github}")
                                    .HandlerFunc(crudAPI.Delete)

	server := http.Server{Addr: ":" + port, Handler: r}

	go func() {
		err := server.ListenAndServe()
		if err != nil && err != http.ErrServerClosed {
			log.Fatalf("could not start server %v", err)
		}
	}()
....
	exit := make(chan os.Signal)
	signal.Notify(exit, syscall.SIGTERM, syscall.SIGINT)
	<-exit
....
	c, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer func() {
		crudAPI.Close()
		cancel()
	}()
	err := server.Shutdown(c)
....
```

Alright, with that aside, it's time to provision the required services and deploy the application. Before that you let's go over some of the pre-requisites

## Pre-requisites

- A [Microsoft Azure account](https://docs.microsoft.com/azure/?WT.mc_id=devto-blog-abhishgu) - go ahead and [sign up for a free one!](https://azure.microsoft.com/free/?WT.mc_id=devto-blog-abhishgu)
- `Azure CLI` or `Azure Cloud Shell` - you can either choose to install the [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu) if you don't have it already (should be quick!) or just use the [Azure Cloud Shell](https://azure.microsoft.com/features/cloud-shell/?WT.mc_id=devto-blog-abhishgu) from your browser.

## Setup Azure Cosmos DB

You need to create an Azure Cosmos DB account with the MongoDB API support enabled along with a Database and Collection. You can use the Azure Portal or the Azure CLI

> Learn more about how to [Work with databases, containers, and items in Azure Cosmos DB](https://docs.microsoft.com/azure/cosmos-db/databases-containers-items?WT.mc_id=devto-blog-abhishgu)

### Use the Azure portal

Follow these steps:

- [Create an Azure Cosmos DB account](https://docs.microsoft.com/azure/cosmos-db/create-mongodb-java?WT.mc_id=devto-blog-abhishgu#create-a-database-account)
- [Add a database and collection](https://docs.microsoft.com/azure/cosmos-db/create-mongodb-java?WT.mc_id=devto-blog-abhishgu#add-a-collection) and get the connection string 

### Use Azure CLI

(same commands can be used for Azure CLI or Azure Cloud Shell)

Export the following environment variables:

```
export RESOURCE_GROUP=[to be filled]
export LOCATION=[to be filled]
export COSMOS_DB_ACCOUNT=[to be filled]
export COSMOS_DB_NAME=[to be filled]
export COSMOS_COLLECTION_NAME=[to be filled]
export SHARDING_KEY_PATH='[to be filled]'
```

Start by creating a [new resource group](https://docs.microsoft.com/cli/azure/group?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-group-create) under which you can place all your resources. Once you're done, you can delete the resource group which in turn will delete all the services

```
az group create --name $RESOURCE_GROUP --location $LOCATION
```

[Create an account](https://docs.microsoft.com/cli/azure/cosmosdb?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-cosmosdb-create) (notice `--kind MongoDB`)

```
az cosmosdb create --resource-group $RESOURCE_GROUP --name abhishgu-mongodb --kind MongoDB
```

[Create the database](https://docs.microsoft.com/cli/azure/cosmosdb/mongodb/database?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-cosmosdb-mongodb-database-create)

```
az cosmosdb mongodb database create --account-name $COSMOS_DB_ACCOUNT --name $COSMOS_DB_NAME --resource-group $RESOURCE_GROUP
```

Finally, [create a collection](https://docs.microsoft.com/cli/azure/cosmosdb/mongodb/collection?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-cosmosdb-mongodb-collection-create) within the database

```
az cosmosdb mongo collection create --account-name $COSMOS_DB_ACCOUNT --database-name $COSMOS_DB_NAME --name $COSMOS_COLLECTION_NAME --resource-group-name $RESOURCE_GROUP --shard $SHARDING_KEY_PATH
```

Get the connection string and save it. You will be using it later

```
az cosmosdb list-connection-strings --name $COSMOS_DB_ACCOUNT --resource-group $RESOURCE_GROUP -o tsv --query connectionStrings[0].connectionString
```

## Deploy to Azure App service

### Build a Docker image for the app

> This step is optional. You can use the pre-built image [`abhirockzz/mongodb-go-app`](https://hub.docker.com/r/abhirockzz/mongodb-go-app), which I have made available on [DockerHub](https://hub.docker.com)

You can use the `Dockerfile` to build your own image and push it to a Docker registry (public/private) of your choice

> [Here is a tutorial](https://docs.microsoft.com/azure/app-service/containers/tutorial-custom-docker-image?WT.mc_id=devto-blog-abhishgu) which provides an example of how to use [Azure Container Registry](https://azure.microsoft.com/services/container-registry/?WT.mc_id=devto-blog-abhishgu) with Azure Web App Service

```
docker build -t [REPOSITORY_NAME/YOUR_DOCKER_IMAGE:TAG] .
//e.g.
docker build -t [abhirockzz/mongodb-go-app] .

//login to your registry
docker login

//push image to registry
docker push [REPOSITORY_NAME/YOUR_DOCKER_IMAGE:TAG]
```

### Setup Azure App Service

It's time to deploy our app to the cloud - let's quickly do that using Azure App service. Start by creating an App Service Plan which defines a set of compute resources for our web app to run. 

> Refer to [the documentation](https://docs.microsoft.com/azure/app-service/overview-hosting-plans?toc=%2fazure%2fapp-service%2fcontainers%2ftoc.json&WT.mc_id=devto-blog-abhishgu) for details on App Service plans

The plan is associated with an `SKU` - just like Cognitive Services, you need to choose an `SKU` (pricing tier) for App Service as well. We will use a small tier (`B1`) for this example.

> Accepted values are `B1, B2, B3, D1, F1, FREE, P1V2, P2V2, P3V2, PC2, PC3, PC4, S1, S2, S3, SHARED`

```
export APP_SERVICE_PLAN_NAME=[to be filled]
export APP_SERVICE_SKU=B1

az appservice plan create --name $APP_SERVICE_PLAN_NAME --resource-group $RESOURCE_GROUP --sku $APP_SERVICE_SKU --is-linux
```

> documentation for [`az appservice plan create`](https://docs.microsoft.com/cli/azure/appservice/plan?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-appservice-plan-create)

Setup environment variables

```
export APP_NAME=[to be filled]
export DOCKER_IMAGE=[to be filled]
export MONGODB_CONNECTION_STRING="[to be filled]"
export DATABASE_NAME=[to be filled]
export COLLECTION_NAME=[to be filled]
```

> please ensure that you use double-quotes (`""`) around the connection string value

[Deploy the application](https://docs.microsoft.com/cli/azure/webapp?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-webapp-create)

```
az webapp create --resource-group $RESOURCE_GROUP --plan $APP_SERVICE_PLAN_NAME --name $APP_NAME --deployment-container-image-name $DOCKER_IMAGE
```

[Add the environment variables as configuration settings](https://docs.microsoft.com/cli/azure/webapp/config/appsettings?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-webapp-config-appsettings-set) required by the application

```
az webapp config appsettings set --resource-group $RESOURCE_GROUP --name $APP_NAME --settings MONGODB_CONNECTION_STRING=$MONGODB_CONNECTION_STRING DATABASE_NAME=$DATABASE_NAME COLLECTION_NAME=$COLLECTION_NAME
```

The API should be deployed in some time. When its complete, go ahead and give it a try!

## Test the API

> I have used `curl` for demonstration, but you can use any other tool of your choice

[Get the endpoint/host name](https://docs.microsoft.com/cli/azure/webapp?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-webapp-show) for the deployed app

```
APP_URL=$(az webapp show --name $APP_NAME --resource-group $RESOURCE_GROUP -o tsv --query 'defaultHostName')
```

Create a few developer profiles

```
curl -X POST -d '{"github_id":"abhirockzz", "blog":"dev.to/abhirockzz", "skills":["java","azure"]}' $APP_URL/developers

curl -X POST -d '{"github_id":"abhishek", "blog":"medium.com/@abhishek1987/", "skills":["go","mongodb"]}' $APP_URL/developers
```

Find a single developer profile using GitHub ID

```
curl $APP_URL/developers/abhirockzz
```

If you search for a profile which does not exist

```
curl $APP_URL/developers/foo

//developer profile with GitHub ID foo does not exist
```

Fetch all dev profiles

```
curl $APP_URL/developers
```

Delete a developer profile

```
curl -X DELETE $APP_URL/developers/abhishek
```

Confirm again

```
curl $APP_URL/developers
```

Once you're done, you can delete the resource group to delete all the services

```
az group delete --name $RESOURCE_GROUP --no-wait
```

That's all for this blog. I would love to have your feedback and suggestions! Simply [tweet](https://twitter.com/abhi_tweeter) or drop a comment. And, if you found this article useful, please like and follow 😃😃