# 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 ```go 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 ```go // 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 ```go // 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 ```go 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) ```go // 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 ```go 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 ```go // 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 ```go 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 ```go // 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 ```go // 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 ```go // 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 ```go // 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 ```go 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: ```go 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: ```go type YourModel struct { Model // Your fields... } // Model already implements Timestampable // Timestamps are automatically set on Create/Update operations ``` ### Custom ID Management ```go 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: ```go // 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 ```go // 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 ```go // Use FindMany with limit for large result sets users, err := userRepo.FindMany(context.Background(), filter, 1000) ``` ### 4. Connection Pooling Configure appropriate connection pool settings: ```go config := mongo.ConnectionConfig{ MaxPoolSize: 100, MinPoolSize: 5, MaxConnIdleTime: 30 * time.Minute, } ``` ## Error Handling The ORM provides specific error types: ```go 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 ```go // 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 ```go 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 ```go // 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: ```go // 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 1. Fork the repository 2. Create a feature branch 3. Add tests for new functionality 4. Ensure all tests pass 5. Submit a pull request ## License This project is licensed under the MIT License - see the LICENSE file for details.