Skip to main content

Overview

The backend uses MongoDB as its primary database with the official go.mongodb.org/mongo-driver. The configs package handles all database initialization and common operations.

MongoDB Connection

MongoInstance Struct

The global database instance is defined in configs/db.go:14-19:
configs/db.go
type MongoInstance struct {
    Client *mongo.Client
    DB     *mongo.Database
}

var MI MongoInstance
Client
*mongo.Client
MongoDB client instance for connection management
DB
*mongo.Database
Active database reference for queries
The MI variable is a global singleton that provides database access throughout the application.

Connection Initialization

The ConnectDB function establishes the MongoDB connection (configs/db.go:21-44):
configs/db.go
func ConnectDB() {
    dbName := fmt.Sprintf("%s-backend", AppEnv())
    serverAPI := options.ServerAPI(options.ServerAPIVersion1)
    opts := options.Client().ApplyURI(EnvMongoURI()).SetServerAPIOptions(serverAPI)
    client, err := mongo.Connect(context.TODO(), opts)
    if err != nil {
        log.Fatalf(err.Error())
    }
    // defer func() {
    //     if err = client.Disconnect(context.TODO()); err != nil {
    //         panic(err)
    //     }
    // }()
    var result bson.M
    if err := client.Database(dbName).RunCommand(context.TODO(), bson.D{{Key: "ping", Value: 1}}).Decode(&result); err != nil {
        log.Fatalf(err.Error())
    }
    fmt.Println("Pinged your deployment. You successfully connected to MongoDB!")

    MI = MongoInstance{
        Client: client,
        DB:     client.Database(dbName),
    }
}

Connection Flow

  1. Database Naming - Dynamically sets database name as {APP_ENV}-backend (e.g., development-backend, production-backend)
  2. Server API Options - Uses MongoDB Server API Version 1 for stable compatibility
  3. Connection Attempt - Connects using URI from environment variable
  4. Health Check - Pings database to verify connection
  5. Global Instance - Stores client and database in MI variable
The disconnection logic is commented out. In production, consider implementing graceful shutdown with defer client.Disconnect() in the main function.

Environment Configuration

Database configuration is retrieved from environment variables (configs/env.go):

MongoDB URI

configs/env.go
func EnvMongoURI() string {
    return os.Getenv("MONGODB_URI")
}
MONGODB_URI
string
required
MongoDB connection string. Supports local MongoDB or MongoDB Atlas.
Examples:
# Local MongoDB
MONGODB_URI=mongodb://localhost:27017

# MongoDB Atlas
MONGODB_URI=mongodb+srv://user:[email protected]/?retryWrites=true&w=majority

# MongoDB with authentication
MONGODB_URI=mongodb://username:password@localhost:27017/admin

Application Environment

configs/env.go
func AppEnv() string {
    return os.Getenv("APP_ENV")
}

func EnvIsProd() bool {
    return AppEnv() == "production"
}
APP_ENV
string
required
Application environment (development, staging, or production). Affects database naming.

Database Operations

Insert Document

The StoreRequestInDb function inserts documents into collections (configs/db.go:46-58):
configs/db.go
func StoreRequestInDb(request any, collection string) (string, error) {
    coll := MI.DB.Collection(collection)
    insertResult, err := coll.InsertOne(context.TODO(), request)
    if err != nil {
        log.Printf("there was an error recording the request \n%v\n", err)
        return "", err
    }
    uuid, ok := insertResult.InsertedID.(primitive.ObjectID)
    if !ok {
        return "", err
    }
    return uuid.Hex(), nil
}
request
any
required
The document to insert (typically a struct)
collection
string
required
Target collection name (e.g., "users", "sessions")
Returns: Hexadecimal string of the inserted document’s ObjectID Usage Example:
user := users.User{
    Email: "[email protected]",
    Status: "active",
    CreatedAt: time.Now(),
}

id, err := configs.StoreRequestInDb(user, "users")
if err != nil {
    // Handle error
}
// id contains the MongoDB ObjectID as hex string

Update Document

The UpdateRequestInDb function updates existing documents (configs/db.go:60-70):
configs/db.go
func UpdateRequestInDb(ID string, request any, collection string) (string, error) {
    coll := MI.DB.Collection(collection)
    filter := bson.M{"_id": ID}
    update := bson.M{"$set": request}
    _, err := coll.UpdateOne(context.TODO(), filter, update)
    if err != nil {
        log.Printf("there was an error updating the request \n%v\n", err)
        return "", err
    }
    return ID, nil
}
ID
string
required
Document identifier (must match the _id field)
request
any
required
Document fields to update (uses $set operator)
collection
string
required
Target collection name
Returns: The document ID if update succeeds
This function expects the _id field to be a string, not a MongoDB ObjectID. Ensure your documents use string IDs or modify the filter accordingly.
Usage Example:
updateData := bson.M{
    "status": "active",
    "updated_at": time.Now(),
}

id, err := configs.UpdateRequestInDb(userID, updateData, "users")
if err != nil {
    // Handle error
}

Query Operations

While the configs package provides insert and update utilities, query operations are typically handled in model packages. Example from users/model.go:32-36:
users/model.go
func GetUserById(id string) User {
    var user User
    _ = configs.MI.DB.Collection("users").FindOne(context.TODO(), bson.M{"_id": id}).Decode(&user)
    return user
}

Common Query Patterns

// Find single document
var user User
err := configs.MI.DB.Collection("users").FindOne(
    context.TODO(),
    bson.M{"email": "[email protected]"},
).Decode(&user)

BSON Types

MongoDB uses BSON (Binary JSON) for document storage. Common BSON types:
bson.M
map[string]any
Unordered map for documents and queries
bson.D
[]bson.E
Ordered document (preserves field order)
primitive.ObjectID
[12]byte
MongoDB’s unique identifier type
Usage:
// Simple query
filter := bson.M{"email": "[email protected]"}

// Complex query with operators
filter := bson.M{
    "status": "active",
    "created_at": bson.M{"$gte": time.Now().AddDate(0, -1, 0)},
}

// Ordered document (for commands)
cmd := bson.D{{Key: "ping", Value: 1}}

Context Management

The codebase currently uses context.TODO() for database operations. For production applications, consider using context with timeouts:
// Create context with 5-second timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// Use in database operation
err := configs.MI.DB.Collection("users").FindOne(ctx, filter).Decode(&user)
Using context.WithTimeout() prevents database operations from hanging indefinitely and improves application resilience.

Collections Used

Current collections in the application:
users
collection
User accounts and authentication data
Collections are created automatically when first accessed. MongoDB creates them lazily on first write operation.

Connection String Examples

Local Development

MONGODB_URI=mongodb://localhost:27017

Docker Compose

MONGODB_URI=mongodb://mongo:27017

MongoDB Atlas

MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.example.mongodb.net/?retryWrites=true&w=majority

Authentication Required

MONGODB_URI=mongodb://admin:password@localhost:27017/admin?authSource=admin

Error Handling

Database operations should handle errors appropriately:
// Connection errors - Fatal (app cannot run without DB)
if err != nil {
    log.Fatalf("Failed to connect to database: %v", err)
}

// Query errors - Return to caller
if err != nil {
    return nil, fmt.Errorf("failed to fetch user: %w", err)
}

// Insert/Update errors - Log and return
if err != nil {
    log.Printf("Database operation failed: %v", err)
    return "", err
}
Avoid using log.Fatalf() for query errors as it terminates the entire application. Reserve fatal logging for initialization failures only.

Database Naming Convention

Databases are automatically named based on environment:
dbName := fmt.Sprintf("%s-backend", AppEnv())
APP_ENVDatabase Name
developmentdevelopment-backend
stagingstaging-backend
productionproduction-backend
This convention ensures environment isolation and prevents accidental data mixing.

Indexes

No indexes are explicitly defined in the codebase. Consider adding indexes for frequently queried fields:
// Create unique index on email field
indexModel := mongo.IndexModel{
    Keys:    bson.D{{Key: "email", Value: 1}},
    Options: options.Index().SetUnique(true),
}

_, err := configs.MI.DB.Collection("users").Indexes().CreateOne(context.TODO(), indexModel)

Next Steps

User Model

Explore the User struct and its database operations

Authentication

See how authentication uses database queries

Build docs developers (and LLMs) love