Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

Introducing MongoDB 8.0, the fastest MongoDB ever!
MongoDB Developer
MongoDB
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Productschevron-right
MongoDBchevron-right

How to Build a Go Web App with Gin, MongoDB, and AI

Ado Kukic10 min read • Published Aug 30, 2024 • Updated Aug 30, 2024
GoMongoDB
Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Building applications with Go provides many advantages. The language is fast, simple, and lightweight while supporting powerful features like concurrency, strong typing, and a robust standard library. In this tutorial, we’ll use the popular Gin web framework along with MongoDB to build a Go-based web application.
Gin is a minimalist web framework for Golang that provides an easy way to build web servers and APIs. It is fast, lightweight, and modular, making it ideal for building microservices and APIs, but can be easily extended to build full-blown applications.
We'll use Gin to build a web application with three endpoints that connect to a MongoDB database. MongoDB is a popular document-oriented NoSQL database that stores data in JSON-like documents. MongoDB is a great fit for building modern applications.
Rather than building the entire application by hand, we’ll leverage a coding AI assistant by Sourcegraph called Cody to help us build our Go application. Cody is the only AI assistant that knows your entire codebase and can help you write, debug, test, and document your code. We’ll use many of these features as we build our application today.

Prerequisites

Before you begin, you’ll need:
  • Go installed on your development machine. Download it on their website.
  • A MongoDB Atlas account. Sign up for free.
  • Basic familiarity with Go and MongoDB syntax.
  • Sourcegraph Cody installed in your favorite IDE. (For this tutorial, we'll be using VS Code). Get it for free.
Once you meet the prerequisites, you’re ready to build. Let’s go.

Getting started

We'll start by creating a new Go project for our application. For this example, we’ll name the project mflix, so let’s go ahead and create the project directory and navigate into it:
1mkdir mflix
2cd mflix
Next, initialize a new Go module, which will manage dependencies for our project:
1go mod init mflix
Now that we have our Go module created, let’s install the dependencies for our project. We’ll keep it really simple and just install the gin and mongodb libraries.
1go get github.com/gin-gonic/gin
2go get go.mongodb.org/mongo-driver/mongo
With our dependencies fetched and installed, we’re ready to start building our application.

Gin application setup with Cody

To start building our application, let’s go ahead and create our entry point into the app by creating a main.go file. Next, while we can set up our application manually, we’ll instead leverage Cody to build out our starting point. In the Cody chat window, we can ask Cody to create a basic Go Gin application.
Create a New Gin App with Cody AI
Cody generated a good starting point for us. It imported the Gin framework, created a main function, and instantiated a basic Gin application with a single route that prints the message Hello World. Good start.
1package main
2
3import (
4 "github.com/gin-gonic/gin"
5)
6
7func main() {
8 r := gin.Default()
9 r.GET("/", func(c *gin.Context) {
10 c.JSON(200, gin.H{
11 "message": "Hello World",
12 })
13 })
14
15 r.Run()
16}
Let’s make sure this code runs. Start up the server by running go run main.go in your terminal window inside the mflix directory and then navigate to localhost:8080, which is the default port for a Gin application. Our code works and the result we should see is:
Sample Gin Webserver Output
We have a great starting point now. Next, let’s add our MongoDB client to our Gin application. We could use Cody again, but for this one, let’s write it ourselves. We’ll update the code to the following:
1package main
2
3import (
4 // Add required Go packages
5 "context"
6 "log"
7
8 "github.com/gin-gonic/gin"
9
10 // Add the MongoDB driver packages
11 "go.mongodb.org/mongo-driver/mongo"
12 "go.mongodb.org/mongo-driver/mongo/options"
13)
14
15// Your MongoDB Atlas Connection String
16const uri = "YOUR-CONNECTION-STRING-HERE"
17
18// A global variable that will hold a reference to the MongoDB client
19var mongoClient *mongo.Client
20
21
22// The init function will run before our main function to establish a connection to MongoDB. If it cannot connect it will fail and the program will exit.
23func init() {
24 if err := connect_to_mongodb(); err != nil {
25 log.Fatal("Could not connect to MongoDB")
26 }
27}
28
29func main() {
30 r := gin.Default()
31 r.GET("/", func(c *gin.Context) {
32 c.JSON(200, gin.H{
33 "message": "Hello World",
34 })
35 })
36
37 r.Run()
38}
39
40// Our implementation logic for connecting to MongoDB
41func connect_to_mongodb() error {
42 serverAPI := options.ServerAPI(options.ServerAPIVersion1)
43 opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI)
44
45 client, err := mongo.Connect(context.TODO(), opts)
46 if err != nil {
47 panic(err)
48 }
49 err = client.Ping(context.TODO(), nil)
50 mongoClient = client
51 return err
52}
Be sure to set your MongoDB Atlas connection string on line 12 in the const uri variable. Otherwise, the program will not run. You can get your MongoDB Atlas connection string by navigating to the Atlas dashboard, clicking the “Connect” button on your database cluster, and selecting the driver you are using.
Connection URI within MongoDB Atlas
If you need more help setting up your MongoDB Atlas cluster and loading in the sample data, check out the “How to Use a Sample Database with MongoDB” guide. The database that we will work with is called sample_mflix and the collection in that database we’ll use is called movies. This dataset contains a list of movies with various information like the plot, genre, year of release, and much more.
MongoDB Atlas Sample Dataset for Movies
Now that we have our MongoDB database set up in our Go application, we are ready to start building our additional endpoints. Since we’ll be working out of the sample dataset that contains movie information, we’ll create three endpoints based on working with movie data:
  • An endpoint to get a list of all the movies.
  • An endpoint to get a single movie based on a provided id.
  • An endpoint to run an aggregation on the movies collection.
We can either do this manually or if you’re new to writing Go applications, you can ask Cody. Let’s ask Cody.
Cody AI Create Endpoints
Cody gave us three ready-to-go endpoints.

Get movies

This endpoint will go into the sample_mflix database, and then into the movies collection, and it’ll retrieve all of the movies.
1// GET /movies - Get all movies
2func getMovies(c *gin.Context) {
3 // Find movies
4 cursor, err := mongoClient.Database("sample_mflix").Collection("movies").Find(context.TODO(), bson.D{{}})
5 if err != nil {
6 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
7 return
8 }
9
10 // Map results
11 var movies []bson.M
12 if err = cursor.All(context.TODO(), &movies); err != nil {
13 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
14 return
15 }
16
17 // Return movies
18 c.JSON(http.StatusOK, movies)
19}

Get movie by ID

The second endpoint will return a specific movie based on the id provided from the movies collection in the sample_mflix database.
1// GET /movies/:id - Get movie by ID
2func getMovieByID(c *gin.Context) {
3 // Get movie ID from URL
4 id := c.Param("id")
5
6 // Find movie by ID
7 var movie bson.M
8 err := mongoClient.Database("sample_mflix").Collection("movies").FindOne(context.TODO(), bson.D{{"_id", id}}).Decode(&movie)
9 if err != nil {
10 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
11 return
12 }
13
14 // Return movie
15 c.JSON(http.StatusOK, movie)
16}

Aggregate movies

The third and final endpoint will allow us to run aggregations on the movies collection. Aggregation operations process multiple documents and return computed results. So with this endpoint, the end user could pass in any valid MongoDB aggregation pipeline to run various analyses on the movies collection.
Note that aggregations are very powerful and in a production environment, you probably wouldn’t want to enable this level of access through HTTP request payloads. But for the sake of the tutorial, we opted to keep it in. As a homework assignment for further learning, try using Cody to limit the number of stages or the types of operations that the end user can perform on this endpoint.
1// POST /movies/aggregations - Run aggregations on movies
2func aggregateMovies(c *gin.Context) {
3 // Get aggregation pipeline from request body
4 var pipeline interface{}
5 if err := c.ShouldBindJSON(&pipeline); err != nil {
6 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
7 return
8 }
9
10 // Run aggregations
11 cursor, err := mongoClient.Database("sample_mflix").Collection("movies").Aggregate(context.TODO(), pipeline)
12 if err != nil {
13 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
14 return
15 }
16
17 // Map results
18 var result []bson.M
19 if err = cursor.All(context.TODO(), &result); err != nil {
20 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
21 return
22 }
23
24 // Return result
25 c.JSON(http.StatusOK, result)
26}
Now that we have our endpoints implemented, let’s add them to our router so that we can call them. Here again, we can use another feature of Cody, called autocomplete, to intelligently give us statement completions so that we don’t have to write all the code ourselves.
Cody AI Autocomplete with Go
Our main function should now look like:
1func main() {
2 r := gin.Default()
3 r.GET("/", func(c *gin.Context) {
4 c.JSON(200, gin.H{
5 "message": "Hello World",
6 })
7 })
8 r.GET("/movies", getMovies)
9 r.GET("/movies/:id", getMovieByID)
10 r.POST("/movies/aggregations", aggregateMovies)
11
12 r.Run()
13}
Now that we have our routes set up, let’s test our application to make sure everything is working well. Restart the server and navigate to localhost:8080/movies. If all goes well, you should see a large list of movies returned in JSON format in your browser window. If you do not see this, check your IDE console to see what errors are shown.
Sample Output for the Movies Endpoint
Let’s test the second endpoint. Pick any id from the movies collection and navigate to localhost:8080/movies/{id} — so for example, localhost:8080/movies/573a1390f29313caabcd42e8. If everything goes well, you should see that single movie listed. But if you’ve been following this tutorial, you actually won’t see the movie.
String to Object ID Results Error
The issue is that in our getMovie function implementation, we are accepting the id value as a string, while the data type in our MongoDB database is an ObjectID. So when we run the FindOne method and try to match the string value of id to the ObjectID value, we don’t get a match.
Let’s ask Cody to help us fix this by converting the string input we get to an ObjectID.
Cody AI MongoDB String to ObjectID
Our updated getMovieByID function is as follows:
1func getMovieByID(c *gin.Context) {
2
3 // Get movie ID from URL
4 idStr := c.Param("id")
5
6 // Convert id string to ObjectId
7 id, err := primitive.ObjectIDFromHex(idStr)
8 if err != nil {
9 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
10 return
11 }
12
13 // Find movie by ObjectId
14 var movie bson.M
15 err = mongoClient.Database("sample_mflix").Collection("movies").FindOne(context.TODO(), bson.D{{"_id", id}}).Decode(&movie)
16 if err != nil {
17 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
18 return
19 }
20
21 // Return movie
22 c.JSON(http.StatusOK, movie)
23}
Depending on your IDE, you may need to add the primitive dependency in your import statement. The final import statement looks like:
1import (
2 "context"
3 "log"
4 "net/http"
5
6 "github.com/gin-gonic/gin"
7 "go.mongodb.org/mongo-driver/bson"
8 "go.mongodb.org/mongo-driver/bson/primitive"
9 "go.mongodb.org/mongo-driver/mongo"
10 "go.mongodb.org/mongo-driver/mongo/options"
11)
If we examine the new code that Cody provided, we can see that we are now getting the value from our id parameter and storing it into a variable named idStr. We then use the primitive package to try and convert the string to an ObjectID. If the idStr is a valid string that can be converted to an ObjectID, then we are good to go and we use the new id variable when doing our FindOne operation. If not, then we get an error message back.
Restart your server and now try to get a single movie result by navigating to localhost:8080/movies/{id}.
Single Movie Response Endpoint
For our final endpoint, we are allowing the end user to provide an aggregation pipeline that we will execute on the mflix collection. The user can provide any aggregation they want. To test this endpoint, we’ll make a POST request to localhost:8080/movies/aggregations. In the body of the request, we’ll include our aggregation pipeline.
Postman Aggregation Endpoint in MongoDB
Let’s run an aggregation to return a count of comedy movies, grouped by year, in descending order. Again, remember aggregations are very powerful and can be abused. You normally would not want to give direct access to the end user to write and run their own aggregations ad hoc within an HTTP request, unless it was for something like an internal tool. Our aggregation pipeline will look like the following:
1[
2 {"$match": {"genres": "Comedy"}},
3 {"$group": {
4 "_id": "$year",
5 "count": {"$sum": 1}
6 }},
7 {"$sort": {"count": -1}}
8]
Running this aggregation, we’ll get a result set that looks like this:
1[
2 {
3 "_id": 2014,
4 "count": 287
5 },
6 {
7 "_id": 2013,
8 "count": 286
9 },
10 {
11 "_id": 2009,
12 "count": 268
13 },
14 {
15 "_id": 2011,
16 "count": 263
17 },
18 {
19 "_id": 2006,
20 "count": 260
21 },
22 ...
23]
It seems 2014 was a big year for comedy. If you are not familiar with how aggregations work, you can check out the following resources:
Additionally, you can ask Cody for a specific explanation about how our aggregateMovies function works to help you further understand how the code is implemented using the Cody /explain command.
Cody AI Explain the Code

Final code

We wrote a Go web server using Gin, MongoDB, and Cody today. While the application may not be the most complex piece of code, we learned how to:
  • Build routes and endpoints using the Gin web framework.
  • Implement MongoDB in our Gin application.
  • Make MongoDB queries to retrieve data.
  • Execute MongoDB aggregations.
  • Leverage Cody to help us write, debug, and explain code.
The final documented output of all the code we’ve written in this post is below for your reference:
1// Declare the entry point into our application
2package main
3
4// Add our dependencies from the standard library, Gin, and MongoDB
5import (
6 "context"
7 "fmt"
8 "log"
9 "net/http"
10
11 "github.com/gin-gonic/gin"
12 "go.mongodb.org/mongo-driver/bson"
13 "go.mongodb.org/mongo-driver/bson/primitive"
14 "go.mongodb.org/mongo-driver/mongo"
15 "go.mongodb.org/mongo-driver/mongo/options"
16)
17
18// Define your MongoDB connection string
19const uri = "{YOUR-CONNECTION-STRING-HERE}"
20
21// Create a global variable to hold our MongoDB connection
22var mongoClient *mongo.Client
23
24// This function runs before we call our main function and connects to our MongoDB database. If it cannot connect, the application stops.
25func init() {
26 if err := connect_to_mongodb(); err != nil {
27 log.Fatal("Could not connect to MongoDB")
28 }
29}
30
31
32// Our entry point into our application
33func main() {
34 // The simplest way to start a Gin application using the frameworks defaults
35 r := gin.Default()
36
37 // Our route definitions
38 r.GET("/", func(c *gin.Context) {
39 c.JSON(200, gin.H{
40 "message": "Hello World",
41 })
42 })
43 r.GET("/movies", getMovies)
44 r.GET("/movies/:id", getMovieByID)
45 r.POST("/movies/aggregations", aggregateMovies)
46
47 // The Run() method starts our Gin server
48 r.Run()
49}
50
51// Implemention of the /movies route that returns all of the movies from our movies collection.
52func getMovies(c *gin.Context) {
53 // Find movies
54 cursor, err := mongoClient.Database("sample_mflix").Collection("movies").Find(context.TODO(), bson.D{{}})
55 if err != nil {
56 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
57 return
58 }
59
60 // Map results
61 var movies []bson.M
62 if err = cursor.All(context.TODO(), &movies); err != nil {
63 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
64 return
65 }
66
67 // Return movies
68 c.JSON(http.StatusOK, movies)
69}
70
71
72// The implementation of our /movies/{id} endpoint that returns a single movie based on the provided ID
73func getMovieByID(c *gin.Context) {
74
75 // Get movie ID from URL
76 idStr := c.Param("id")
77
78 // Convert id string to ObjectId
79 id, err := primitive.ObjectIDFromHex(idStr)
80 if err != nil {
81 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
82 return
83 }
84
85 // Find movie by ObjectId
86 var movie bson.M
87 err = mongoClient.Database("sample_mflix").Collection("movies").FindOne(context.TODO(), bson.D{{"_id", id}}).Decode(&movie)
88 if err != nil {
89 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
90 return
91 }
92
93 // Return movie
94 c.JSON(http.StatusOK, movie)
95}
96
97// The implementation of our /movies/aggregations endpoint that allows a user to pass in an aggregation to run our the movies collection.
98func aggregateMovies(c *gin.Context) {
99 // Get aggregation pipeline from request body
100 var pipeline interface{}
101 if err := c.ShouldBindJSON(&pipeline); err != nil {
102 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
103 return
104 }
105
106 // Run aggregations
107 cursor, err := mongoClient.Database("sample_mflix").Collection("movies").Aggregate(context.TODO(), pipeline)
108 if err != nil {
109 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
110 return
111 }
112
113 // Map results
114 var result []bson.M
115 if err = cursor.All(context.TODO(), &result); err != nil {
116 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
117 return
118 }
119
120 // Return result
121 c.JSON(http.StatusOK, result)
122}
123
124
125// Our implementation code to connect to MongoDB at startup
126func connect_to_mongodb() error {
127 serverAPI := options.ServerAPI(options.ServerAPIVersion1)
128 opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI)
129
130 client, err := mongo.Connect(context.TODO(), opts)
131 if err != nil {
132 panic(err)
133 }
134 err = client.Ping(context.TODO(), nil)
135 mongoClient = client
136 return err
137}

Conclusion

Go is an amazing programming language and Gin is a very powerful framework for building web applications. Combined with MongoDB and the native MongoDB driver, and with a little help from Cody, we were able to build this app in no time at all.
Cody is the only AI assistant that knows your entire codebase. In this tutorial, we barely scratched the surface of what’s possible. Beyond autocomplete and the commands we showed today, Cody can identify code smells, document your code, create unit tests, and support the creation of custom commands to extend it to whatever use case you have. Give Cody a try for free at cody.dev.
And if you have any questions or comments, let’s continue the conversation in our developer forums!
The entire code for our application is above, so there is no GitHub repo for this simple application. Happy coding.

Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Tutorial

MongoDB Time Series With C++


Sep 17, 2024 | 6 min read
Tutorial

Ensuring High Availability for MongoDB on Kubernetes


Jul 12, 2024 | 11 min read
Quickstart

Aggregation Framework with Node.js Tutorial


Oct 01, 2024 | 9 min read
Article

Using MongoDB Change Streams in Java


Aug 28, 2024 | 6 min read
Table of Contents
  • Prerequisites