Building a GORM-like ORM for MongoDB with Golang

Abhishek Ranjan
4 min readMar 28, 2023

--

Introduction

Object-Relational Mapping (ORM) is a programming technique that simplifies the interaction between an object-oriented language and a relational database management system (RDBMS). GORM is a popular ORM library for Golang that supports RDBMSs like MySQL, PostgreSQL, and SQLite. In this article, we’ll explore building a GORM-like ORM for MongoDB, a NoSQL database, to simplify interaction with MongoDB using Golang. We’ll refer to this system as “MONGORM.”

Since MongoDB stores data in a document-based format, the mapping process in our case will be Object-Document Mapping (ODM). Let’s dive into creating MONGORM, a Golang-based ORM for MongoDB.

Setup and Dependencies

To start building MONGORM, you’ll need to have Golang installed and set up. You can follow the official Golang installation guide (https://golang.org/doc/install) for this purpose.

Next, you’ll need to install the MongoDB Go Driver. You can do this using the following command:

go get go.mongodb.org/mongo-driver

Establishing a Connection

First, let’s create a function to establish a connection to a MongoDB instance. Create a new Go file named mongorm.go, and add the following code:

package mongorm

import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
)

func Connect(uri string) (*mongo.Client, error) {
clientOptions := options.Client().ApplyURI(uri)
client, err := mongo.Connect(context.Background(), clientOptions)

if err != nil {
log.Fatal(err)
}

err = client.Ping(context.Background(), nil)
if err != nil {
log.Fatal(err)
}

fmt.Println("Successfully connected to MongoDB")
return client, nil
}

Defining Models

Create a new Go file named model.go and define the base model structure that will be embedded in all other models. This structure will include an ID field and timestamp fields.

package mongorm

import (
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)

type Model struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
CreatedAt time.Time `bson:"created_at"`
UpdatedAt time.Time `bson:"updated_at"`
}

CRUD Operations

Next, we’ll implement basic CRUD operations in mongorm.go that will be used by all models. These operations include Create, Read, Update, and Delete.

func (m *Model) Create(ctx context.Context, db *mongo.Database, collectionName string, model interface{}) error {
collection := db.Collection(collectionName)

m.CreatedAt = time.Now()
m.UpdatedAt = time.Now()

res, err := collection.InsertOne(ctx, model)
if err != nil {
return err
}

m.ID = res.InsertedID.(primitive.ObjectID)
return nil
}

func (m *Model) Read(ctx context.Context, db *mongo.Database, collectionName string, filter interface{}, result interface{}) error {
collection := db.Collection(collectionName)

err := collection.FindOne(ctx, filter).Decode(result)
if err != nil {
return err
}

return nil
}

func (m *Model) Update(ctx context.Context, db *mongo.Database, collectionName string, filter interface{}, update interface{}) error {
collection := db.Collection(collectionName)

m.UpdatedAt = time.Now()

_, err := collection.UpdateOne(ctx, filter, update)
if err != nil {
return err
}

return nil
}

func (m *Model) Delete(ctx context.Context, db *mongo.Database, collectionName string, filter interface{}) error {
collection := db.Collection(collectionName)
_, err := collection.DeleteOne(ctx, filter)
if err != nil {
return err
}

return nil
}

Usage Example

Let’s create a simple example to demonstrate the usage of MONGORM. Assume we have a `User` model with the following structure:

package main

import "github.com/yourusername/mongorm"

type User struct {
mongorm.Model
FirstName string `bson:"first_name"`
LastName string `bson:"last_name"`
Email string `bson:"email"`
}

Now, let’s perform basic CRUD operations using MONGORM with the User model:

package main

import (
"context"
"fmt"
"github.com/yourusername/mongorm"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)

func main() {
// Replace the connection string with your own
client, err := mongorm.Connect("mongodb://localhost:27017")
if err != nil {
panic(err)
}

db := client.Database("test_db")

// Create a new user
user := User{
FirstName: "John",
LastName: "Doe",
Email: "john.doe@example.com",
}
err = user.Create(context.Background(), db, "users", &user)
if err != nil {
panic(err)
}
fmt.Printf("User created: %v\n", user)

// Read a user by ID
var readUser User
err = readUser.Read(context.Background(), db, "users", bson.M{"_id": user.ID}, &readUser)
if err != nil {
panic(err)
}
fmt.Printf("User read: %v\n", readUser)

// Update a user's email
update := bson.M{"$set": bson.M{"email": "john.doe_updated@example.com", "updated_at": primitive.NewDateTimeFromTime(user.UpdatedAt)}}
err = user.Update(context.Background(), db, "users", bson.M{"_id": user.ID}, update)
if err != nil {
panic(err)
}
fmt.Printf("User updated: %v\n", user)

// Delete a user by ID
err = user.Delete(context.Background(), db, "users", bson.M{"_id": user.ID})
if err != nil {
panic(err)
}
fmt.Println("User deleted")
}

This example demonstrates how to use MONGORM to create, read, update, and delete documents in a MongoDB collection using a Golang struct. You can extend MONGORM to support more advanced features like query building, associations, and transactions, among others.

Conclusion

In this article, we explored building a GORM-like ORM for MongoDB called MONGORM using Golang. MONGORM simplifies interactions with MongoDB by providing a clean, object-oriented approach. While the example provided here covers basic CRUD operations, you can further extend MONGORM to support more advanced features according to your project’s requirements.

--

--