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

Join us at AWS re:Invent 2024! Learn how to use MongoDB for AI use cases.
MongoDB Developer
Go
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Languageschevron-right
Gochevron-right

HTTP Servers Persisting Data in MongoDB

Jorge D. Ortiz-Fuentes5 min read • Published Sep 04, 2024 • Updated Sep 04, 2024
Go
FULL APPLICATION
Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
In the previous article and the corresponding video, we wrote a basic HTTP server from scratch. We used Go 1.22's new capabilities to deal with different HTTP verbs and we deserialized data that was sent from an HTTP client.
Exchanging data is worthless if you forget it right away. We are going to persist that data using MongoDB. You will need a MongoDB Atlas cluster. The free one is more than enough. If you don't have an account, you can find guidance on how this is done on this workshop or YouTube. You don't have to do the whole lab, just the parts "Create an Account" and "Create a Cluster" in the "MongoDB Atlas" section. Call your cluster "NoteKeeper" in a FREE cluster. Create a username and password which you will use in a moment. Verify that your IP address is included. Verify that your server's IP address is allowed access. If you use the codespace, include the address 0.0.0.0 to indicate that access is allowed to any IP.

Connect to MongoDB Atlas from Go

  1. So far, we have used packages of the standard library, but we would like to use the MongoDB driver to connect to our Atlas cluster. This adds the MongoDB Go driver to the dependencies of our project, including entries in go.mod for it and all of its dependencies. It also keeps hashes of the dependencies in go.sum to ensure integrity and downloads all the code to be able to include it in the program.
    1go get go.mongodb.org/mongo-driver/mongo
  2. MongoDB uses BSON to serialize and store the data. It is more efficient and supports more types than JSON (we are looking at you, dates, but also BinData). And we can use the same technique that we used for deserializing JSON for converting to BSON, but in this case, the conversion will be done by the driver. We are going to declare a global variable to hold the connection to MongoDB Atlas and use it from the handlers. That is not a best practice. Instead, we could define a type that holds the client and any other dependencies and provides methods –which will have access to the dependencies– that can be used as HTTP handlers.
    1var mdbClient *mongo.Client
  3. If your editor has any issues importing the MongoDB driver packages, you need to have these two in your import block.
    1"go.mongodb.org/mongo-driver/mongo"
    2"go.mongodb.org/mongo-driver/mongo/options"
  4. In the main function, we initialize the connection to Atlas. Notice that this function returns two things. For the first one, we are using a variable that has already been defined at the global scope. The second one, err, isn't defined in the current scope, so we could potentially use the short variable declaration here. However, if we do, it will ignore the global variable that we created for the client (mdbClient) and define a local one only for this scope. So let's use a regular assignment and we need err to be declared to be able to assign a value to it.
    1var err error
    2mdbClient, err = mongo.Connect(ARG1, ARG2)
  5. The first argument of that Connect() call is a context that allows sharing data and cancellation requests between the main function and the client. Let's create one that is meant to do background work. You could add a cancellation timer to this context, among other things.
    1ctxBg := context.Background()
  6. The second argument is a struct that contains the options used to create the connection. The bare minimum is to have a URI to our Atlas MongoDB cluster. We get that URI from the cluster page by clicking on "Get Connection String." We create a constant with that connection string. Don't use this one. It won't work. Get it from your cluster. Having the connection URI with user the and password as a constant isn't a best practice either. You should pass this data using an environment variable instead.
    1const connStr string = "mongodb+srv://yourusername:yourpassword@notekeeper.xxxxxx.mongodb.net/?retryWrites=true&w=majority&appName=NoteKeeper"
  7. We can now use that constant to create the second argument in place.
    1var err error
    2mdbClient, err = mongo.Connect(ctxBg, options.Client().ApplyURI(connStr))
  8. If we cannot connect to Atlas, there is no point in continuing, so we log the error and exit. log.Fatal() takes care of both things.
    1if err != nil {
    2 log.Fatal(err)
    3}
  9. If the connection has been successful, the first thing that we want to do is to ensure that it will be closed if we leave this function. We use defer for that. Everything that we defer will be executed when it exits that function scope, even if things go badly and a panic takes place. We enclose the work in an anonymous function and we call it because defer is a statement. This way, we can use the return value of the Disconnect() method and act accordingly.
    1defer func() {
    2 if err = mdbClient.Disconnect(ctxBg); err != nil {
    3 panic(err)
    4 }
    5}()

Persist data in MongoDB Atlas from Go

Persistence
  1. Then, we want to use the collection (roughly equivalent to a table in a relational database) that will contain our notes in the NoteKeeper database. The first time we refer to this collection, it gets created. And this can be done because there is no need to define the schema of that collection before adding data to it. We are going to access the collection from within the HTTP handler implemented in CreateNote() right before we write to the response writer of the previous code.
    1notesCollection := mdbClient.Database("NoteKeeper").Collection("Notes")
  2. In the next line, we insert the note that has been obtained by deserializing the data in the request. Also from the HTTP request, we obtain the context that was used with it, to extend its use to the request to Atlas.
    1result, err := notesCollection.InsertOne(r.Context(), note)
  3. Should there be any problems with the InsertOne() request, the handler will have to return an err and the proper HTTP status. This has to be done before anything is written to the response writer or it will be ignored. It is not a best practice to return the database error to the user. You might be revealing too much information.
    1if err != nil {
    2 http.Error(w, err.Error(), http.StatusInternalServerError)
    3 return
    4}
  4. And if all goes well, we print the ID of the new entry, at the bottom of the HTTP handler.
    1log.Printf("Id: %v", result.InsertedID)
  5. Compile and run. Then, we use the same request that we had before, only this time, the data will also be persisted to the database in the cloud.
    1curl -iX POST -d '{ "title": "Master plan", "tags": ["ai","users"], "text": "ubiquitous AI", "scope": {"project": "world domination", "area":"strategy"} }' localhost:8081/notes

Conclusion

In this article, we have covered:
  • The use of packages to easily incorporate additional functionality (such as the MongoDB driver).
  • The implementation of error handling in an HTTP handler.
  • The persistence of moderately complex data in a cloud service.
These ideas can be trivially extended to implement a full-featured REST API, a back-end server, or a microservice, so you might consider this your first step to real Go super-powers.
In the next article of this series, we will pay attention to a hairy detail: concurrency. We will use goroutines and channels to implement a graceful shutdown of our server. The repository has all the code for this series and the next ones so you can follow along.
Stay curious. Hack your code. See you next time!
Top Comments in Forums
There are no comments on this article yet.
Start the Conversation

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

Interact With MongoDB in an AWS Lambda Function Using Go


Aug 29, 2024 | 6 min read
Tutorial

Go to MongoDB Using Kafka Connectors - Ultimate Agent Guide


Sep 17, 2024 | 7 min read
Tutorial

HTTP basics With Go 1.22


Apr 23, 2024 | 7 min read
Quickstart

Multi-Document ACID Transactions in MongoDB with Go


Apr 03, 2024 | 6 min read
Table of Contents
  • Connect to MongoDB Atlas from Go