MongoDB ORM for Go
A production-grade, high-performance generic ORM for MongoDB in Go, using the official MongoDB driver. This ORM provides a clean, type-safe interface for MongoDB operations with support for pagination, transactions, and advanced querying.
Features
✅ Generic Repository Pattern - Type-safe operations for any struct
✅ Dual Pagination Support - Both offset-based and cursor-based pagination
✅ Comprehensive CRUD Operations - Create, Read, Update, Delete with bulk operations
✅ Advanced Querying - Fluent query builder with complex conditions
✅ Aggregation Support - Full MongoDB aggregation pipeline support
✅ Connection Management - Connection pooling and lifecycle management
✅ Index Management - Easy index creation and management
✅ Transaction Support - ACID transactions for multi-document operations
✅ Structured Logging - Comprehensive logging with structured fields
✅ Performance Optimized - Efficient cursor-based pagination and connection pooling
✅ Type Safety - Full Go generics support for compile-time type safety
Quick Start
1. Define Your Model
type User struct {
Model
Email string `bson:"email" json:"email"`
FirstName string `bson:"first_name" json:"first_name"`
LastName string `bson:"last_name" json:"last_name"`
Age int `bson:"age" json:"age"`
IsActive bool `bson:"is_active" json:"is_active"`
}
2. Set Up Connection
// Create logger
logger := &YourLogger{}
// Configure connection
config := mongo.ConnectionConfig{
URI: "mongodb://localhost:27017",
Database: "your_database",
}
// Create connection manager
connManager, err := mongo.NewConnectionManager(config, logger)
if err != nil {
log.Fatal(err)
}
defer connManager.Close(context.Background())
// Create repository
userRepo := mongo.NewRepository[User](connManager.GetCollection("users"), logger)
3. Basic Operations
// Create
user := &User{
Email: "john@example.com",
FirstName: "John",
LastName: "Doe",
Age: 30,
IsActive: true,
}
err := userRepo.Create(context.Background(), user)
// Find by ID
foundUser, err := userRepo.FindByID(context.Background(), user.ID)
// Update
user.FirstName = "Jane"
err = userRepo.Update(context.Background(), user)
// Delete
err = userRepo.Delete(context.Background(), user.ID)
Pagination
Offset-based Pagination
pagination := mongo.NewPaginationBuilder().
Limit(10).
Skip(20).
SortDesc("created_at").
Build()
results, err := userRepo.FindAll(context.Background(), bson.M{}, pagination)
Cursor-based Pagination (Recommended for Performance)
// First page
pagination := mongo.NewPaginationBuilder().
Limit(10).
SortDesc("created_at").
Build()
firstPage, err := userRepo.FindAll(context.Background(), bson.M{}, pagination)
// Next page using cursor
if firstPage.NextCursor != "" {
nextPagination := mongo.NewPaginationBuilder().
Limit(10).
SortDesc("created_at").
Cursor(firstPage.NextCursor).
Build()
nextPage, err := userRepo.FindAll(context.Background(), bson.M{}, nextPagination)
}
Advanced Querying
Using QueryBuilder
query := mongo.NewQueryBuilder().
Where("is_active", true).
WhereGreaterThan("age", 18).
WhereIn("category", []interface{}{"admin", "user"}).
WhereRegex("email", ".*@example\\.com", "i").
Build()
results, err := userRepo.FindAll(context.Background(), query, pagination)
Complex Queries
// AND/OR conditions
query := mongo.NewQueryBuilder().
WhereAnd(
bson.M{"is_active": true},
bson.M{"age": bson.M{"$gte": 18}},
).
WhereOr(
bson.M{"role": "admin"},
bson.M{"role": "moderator"},
).
Build()
Aggregation
pipeline := mongo.Pipeline{
{{Key: "$match", Value: bson.M{"is_active": true}}},
{{Key: "$group", Value: bson.M{
"_id": "$role",
"count": bson.M{"$sum": 1},
"avgAge": bson.M{"$avg": "$age"},
}}},
{{Key: "$sort", Value: bson.M{"count": -1}}},
}
results, err := userRepo.Aggregate(context.Background(), pipeline)
Bulk Operations
// Bulk create
users := []User{
{Email: "user1@example.com", FirstName: "User1"},
{Email: "user2@example.com", FirstName: "User2"},
}
err := userRepo.BulkCreate(context.Background(), users)
// Bulk update
filter := bson.M{"is_active": false}
update := bson.M{"$set": bson.M{"status": "inactive"}}
err = userRepo.BulkUpdate(context.Background(), filter, update)
// Bulk delete
filter := bson.M{"created_at": bson.M{"$lt": time.Now().AddDate(0, -1, 0).Unix()}}
err = userRepo.BulkDelete(context.Background(), filter)
Index Management
Create Indexes
// Create common indexes
indexes := mongo.UserIndexes()
err := connManager.CreateIndexes("users", indexes)
// Create custom indexes
customIndexes := []mongo.Index{
*mongo.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}),
*mongo.CreateTTLIndex("expires_at_idx", "expires_at", 3600),
*mongo.CreateTextIndex("search_idx", "title", "description"),
}
err = connManager.CreateIndexes("documents", customIndexes)
Index Types
// Unique index
uniqueIdx := mongo.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}})
// TTL index
ttlIdx := mongo.CreateTTLIndex("expires_at_idx", "expires_at", 3600)
// Text index
textIdx := mongo.CreateTextIndex("search_idx", "title", "description")
// Compound index
compoundIdx := mongo.CreateCompoundIndex("user_status_idx", map[string]int{
"user_id": 1,
"status": 1,
})
// Geospatial index
geoIdx := mongo.CreateGeospatialIndex("location_idx", "location")
// Hashed index
hashIdx := mongo.CreateHashedIndex("user_id_hash_idx", "user_id")
Transactions
// Start session
session, err := connManager.client.StartSession()
if err != nil {
log.Fatal(err)
}
defer session.EndSession(context.Background())
// Execute transaction
_, err = session.WithTransaction(context.Background(), func(sessCtx mongo.SessionContext) (interface{}, error) {
// Create user
user := &User{Email: "transaction@example.com"}
if err := userRepo.Create(sessCtx, user); err != nil {
return nil, err
}
// Create related document
profile := &Profile{UserID: user.ID}
if err := profileRepo.Create(sessCtx, profile); err != nil {
return nil, err
}
return nil, nil
})
Connection Configuration
config := mongo.ConnectionConfig{
URI: "mongodb://localhost:27017",
Database: "your_database",
MaxPoolSize: 100,
MinPoolSize: 5,
MaxConnIdleTime: 30 * time.Minute,
ConnectTimeout: 10 * time.Second,
ServerSelectionTimeout: 5 * time.Second,
}
Logger Interface
Implement the Logger interface for structured logging:
type YourLogger struct{}
func (l *YourLogger) Info(message string, fields map[string]interface{}) {
log.Printf("[INFO] %s: %+v", message, fields)
}
func (l *YourLogger) Error(message string, fields map[string]interface{}) {
log.Printf("[ERROR] %s: %+v", message, fields)
}
func (l *YourLogger) Debug(message string, fields map[string]interface{}) {
log.Printf("[DEBUG] %s: %+v", message, fields)
}
func (l *YourLogger) Warn(message string, fields map[string]interface{}) {
log.Printf("[WARN] %s: %+v", message, fields)
}
Model Interfaces
Timestampable Interface
Implement for automatic timestamp management:
type YourModel struct {
Model
// Your fields...
}
// Model already implements Timestampable
// Timestamps are automatically set on Create/Update operations
Custom ID Management
type CustomModel struct {
Model
// Your fields...
}
// Model already implements IDGetter and IDSetter
// ID is automatically set after Create operations
Performance Best Practices
1. Use Cursor-based Pagination
Cursor-based pagination is more efficient than offset-based pagination for large datasets:
// Good - Cursor-based
pagination := mongo.NewPaginationBuilder().
Limit(10).
SortDesc("created_at").
Build()
// Avoid - Offset-based for large datasets
pagination := mongo.NewPaginationBuilder().
Limit(10).
Skip(10000). // Expensive for large datasets
Build()
2. Create Appropriate Indexes
// Create indexes for frequently queried fields
indexes := []mongo.Index{
*mongo.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}),
*mongo.NewIndex("status_created_idx", bson.D{
{Key: "status", Value: 1},
{Key: "created_at", Value: -1},
}),
}
3. Use Projection for Large Documents
// Use FindMany with limit for large result sets
users, err := userRepo.FindMany(context.Background(), filter, 1000)
4. Connection Pooling
Configure appropriate connection pool settings:
config := mongo.ConnectionConfig{
MaxPoolSize: 100,
MinPoolSize: 5,
MaxConnIdleTime: 30 * time.Minute,
}
Error Handling
The ORM provides specific error types:
var (
ErrDocumentNotFound = errors.New("document not found")
ErrInvalidCursor = errors.New("invalid cursor")
ErrInvalidPagination = errors.New("invalid pagination parameters")
)
// Handle specific errors
user, err := userRepo.FindByID(ctx, id)
if err != nil {
if errors.Is(err, mongo.ErrDocumentNotFound) {
// Handle not found
return
}
// Handle other errors
}
Testing
Unit Testing with Mocks
// Create mock repository for testing
type MockUserRepository struct {
users map[string]*User
}
func (m *MockUserRepository) FindByID(ctx context.Context, id string) (*User, error) {
if user, exists := m.users[id]; exists {
return user, nil
}
return nil, mongo.ErrDocumentNotFound
}
// Use in tests
mockRepo := &MockUserRepository{users: make(map[string]*User)}
Integration Testing
func TestUserRepository(t *testing.T) {
// Set up test database
config := mongo.ConnectionConfig{
URI: "mongodb://localhost:27017",
Database: "test_database",
}
connManager, err := mongo.NewConnectionManager(config, logger)
require.NoError(t, err)
defer connManager.Close(context.Background())
userRepo := mongo.NewRepository[User](connManager.GetCollection("users"), logger)
// Run tests...
}
Migration from Existing Code
From Manual MongoDB Operations
// Before
collection := client.Database("db").Collection("users")
filter := bson.M{"email": email}
var user User
err := collection.FindOne(ctx, filter).Decode(&user)
// After
userRepo := mongo.NewRepository[User](collection, logger)
user, err := userRepo.FindOne(ctx, bson.M{"email": email})
From Other ORMs
The generic repository pattern makes migration straightforward:
// Replace existing repository interface
type UserRepository interface {
FindByID(ctx context.Context, id string) (*User, error)
Create(ctx context.Context, user *User) error
// ... other methods
}
// Implementation remains the same, just use mongo.NewRepository
Contributing
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
This project is licensed under the MIT License - see the LICENSE file for details.