A secure, lightweight, and easy-to-use MongoDB ODM (Object-Document Mapper) for Go with built-in NoSQL injection protection, comprehensive aggregation pipeline support, and one-line operations.
- 🔒 Security First: Built-in NoSQL injection protection and comprehensive input validation
- 🚀 One-Line Operations: Minimal configuration, maximum productivity
- 📦 Zero Dependencies: Only uses the official MongoDB Go driver
- ⚡ High Performance: Optimized queries with intelligent connection pooling
- 🔍 Advanced Querying: Complete support for filtering, sorting, pagination, and projection
- 🛡️ Robust Error Handling: Comprehensive error types and detailed validation
- 🔄 Full CRUD Operations: Complete Create, Read, Update, Delete functionality
- 📊 Comprehensive Aggregation: Full MongoDB aggregation pipeline support with 25+ stages
- 🧮 Fluent Pipeline Builder: Intuitive API for building complex aggregation pipelines
- 🗂️ Index Management: Complete index creation, management, and optimization tools
- ⏱️ Context Support: Full timeout and cancellation support for all operations
- 🧪 Thoroughly Tested: Comprehensive test suite with extensive security scenarios
- 🌍 Geospatial Support: Built-in geospatial queries and aggregations
go get github.com/RezaArani/simongodmpackage main
import (
"fmt"
"time"
"github.com/RezaArani/simongodm/controller"
"go.mongodb.org/mongo-driver/bson"
)
type User struct {
ID string `bson:"_id,omitempty" json:"id,omitempty"`
Name string `bson:"name" json:"name"`
Email string `bson:"email" json:"email"`
Age int `bson:"age" json:"age"`
}
func main() {
// Initialize with minimal configuration
config := controller.DefaultConfig()
db := controller.Newsimongodm(config, "myapp")
defer db.CloseMongoClient()
// Insert a document (one line!)
user := User{Name: "John Doe", Email: "john@example.com", Age: 30}
result, _ := db.InsertOne("users", user)
fmt.Printf("Inserted: %v\n", result.UpsertedID)
// Get a document (one line!)
singleResult, _ := db.GetData("users", db.WithMatch(bson.M{"email": "john@example.com"}))
var foundUser User
singleResult.Decode(&foundUser)
fmt.Printf("Found: %s\n", foundUser.Name)
// Update with security validation (one line!)
db.SaveData("users", bson.M{"age": 31}, db.WithMatch(bson.M{"email": "john@example.com"}), db.WithUpsert(false))
// Delete safely (one line!)
db.DeleteOne("users", db.WithMatch(bson.M{"email": "john@example.com"}))
}- Installation
- Configuration
- Core Operations
- Security Features
- Advanced Aggregation
- Index Management
- Query Options
- API Reference
- Examples
- Testing
- Performance
- Contributing
- License
config := controller.DefaultConfig()
// Uses environment variables or defaults:
// MONGODB_URI (default: "mongodb://localhost:27017")
// DB_NAME (default: "default")config := controller.Config{
MongoURI: "mongodb://localhost:27017",
ConnectTimeout: time.Second * 30,
QueryTimeout: time.Second * 10,
MaxPoolSize: 100,
MinPoolSize: 5,
}
db := controller.Newsimongodm(config, "your_database")export MONGODB_URI="mongodb://username:password@localhost:27017"
export DB_NAME="production_db"// Insert one document
user := User{Name: "Alice", Email: "alice@example.com"}
result, err := db.InsertOne("users", user)
// Insert multiple documents
users := []interface{}{user1, user2, user3}
result, err := db.InsertMany("users", users)// Get single document
var user User
singleResult, err := db.GetData("users", db.WithMatch(bson.M{"email": "alice@example.com"}))
err = singleResult.Decode(&user)
// Get multiple documents with pagination
result, err := db.GetMany("users",
db.WithMatch(bson.M{"active": true}),
db.WithPage(1, 20),
db.WithSortDesc("created_at"))
// Count documents
count, err := db.Count("users", db.WithMatch(bson.M{"active": true}))
// Check if document exists
exists, err := db.Exists("users", db.WithMatch(bson.M{"email": "alice@example.com"}))// Update documents
updateData := bson.M{"last_login": time.Now(), "active": true}
query := bson.M{"email": "alice@example.com"}
// Update existing (upsert: false)
result, err := db.SaveData("users", updateData,
db.WithMatch(query),
db.WithUpsert(false))
// Upsert (insert if not exists)
result, err := db.SaveData("users", updateData,
db.WithMatch(query),
db.WithUpsert(true))// Delete one document
result, err := db.DeleteOne("users", db.WithMatch(bson.M{"email": "alice@example.com"}))
// Delete multiple documents
result, err := db.DeleteData("users",
db.WithMatch(bson.M{"active": false}),
db.WithAllowBulkDelete(true))// ❌ These dangerous queries are automatically blocked:
dangerousQuery := bson.M{
"$where": "this.email == 'admin@example.com'",
}
// ✅ Safe queries are validated and sanitized:
safeQuery := bson.M{
"email": bson.M{"$regex": "^admin", "$options": "i"},
}- Collection Names: Validates against invalid characters and length
- ObjectIDs: Automatic validation and conversion
- Query Depth: Prevents deeply nested query attacks
- Operator Whitelist: Only allows safe MongoDB operators
- String Sanitization: Removes dangerous characters
The ODM automatically blocks these dangerous operators:
$where$expr$function$accumulator- Any unlisted operators starting with
$
SimonGODM provides comprehensive MongoDB aggregation pipeline support with 25+ stages and complete security validation.
| Stage | Description | Builder Method |
|---|---|---|
$match |
Filter documents | AddMatch() |
$project |
Select/transform fields | AddProject() |
$group |
Group and aggregate | AddGroup() |
$sort |
Sort documents | AddSort() |
$limit |
Limit results | AddLimit() |
$skip |
Skip documents | AddSkip() |
$unwind |
Deconstruct arrays | AddUnwind(), AddUnwindWithIndex() |
$lookup |
Join collections | AddLookup(), AddLookupWithPipeline() |
$addFields/$set |
Add computed fields | AddAddFields(), AddSet() |
$unset |
Remove fields | AddUnset() |
$count |
Count documents | AddCount() |
$facet |
Multi-facet analysis | AddFacet() |
$bucket |
Bucket grouping | AddBucket() |
$bucketAuto |
Auto bucket grouping | AddBucketAuto() |
$sortByCount |
Sort by count | AddSortByCount() |
$geoNear |
Geospatial queries | AddGeoNear(), AddGeoNearWithQuery() |
$densify |
Fill data gaps | AddDensify() |
$replaceRoot |
Replace document root | AddReplaceRoot() |
$replaceWith |
Replace with expression | AddReplaceWith() |
$fill |
Fill missing values | Supported |
$setWindowFields |
Window functions | Supported |
// Complex analytics pipeline
pipeline := db.NewPipeline()
// 1. Filter recent orders
pipeline = db.AddMatch(pipeline, bson.M{
"order_date": bson.M{"$gte": time.Now().AddDate(0, -1, 0)},
"status": "completed",
})
// 2. Join with customer data
pipeline = db.AddLookup(pipeline, "customers", "customer_id", "_id", "customer")
pipeline = db.AddUnwind(pipeline, "$customer", false)
// 3. Group by customer country
pipeline = db.AddGroup(pipeline, bson.M{
"_id": "$customer.country",
"total_revenue": bson.M{"$sum": "$amount"},
"order_count": bson.M{"$sum": 1},
"avg_order": bson.M{"$avg": "$amount"},
})
// 4. Add calculated fields
pipeline = db.AddAddFields(pipeline, bson.M{
"revenue_per_order": bson.M{"$divide": []interface{}{"$total_revenue", "$order_count"}},
})
// 5. Sort and limit
pipeline = db.AddSort(pipeline, bson.D{{"total_revenue", -1}})
pipeline = db.AddLimit(pipeline, 10)
result, err := db.Aggregate("orders", pipeline)// Geospatial aggregation with $geoNear
geoPipeline := db.NewPipeline()
geoPipeline = db.AddGeoNear(geoPipeline,
bson.M{"type": "Point", "coordinates": []float64{-74.006, 40.7128}}, // NYC
"distance", 1000000, true) // 1000km radius
geoPipeline = db.AddMatch(geoPipeline, bson.M{"distance": bson.M{"$lte": 500000}})
geoResult, err := db.Aggregate("stores", geoPipeline)// Advanced faceted search
facetPipeline := db.NewPipeline()
facets := map[string][]interface{}{
"by_category": []interface{}{
bson.M{"$group": bson.M{"_id": "$category", "count": bson.M{"$sum": 1}}},
bson.M{"$sort": bson.D{{"count", -1}}},
},
"by_price_range": []interface{}{
bson.M{"$bucket": bson.M{
"groupBy": "$price",
"boundaries": []interface{}{0, 50, 100, 500, 1000},
"default": "expensive",
"output": bson.M{"count": bson.M{"$sum": 1}},
}},
},
"statistics": []interface{}{
bson.M{"$group": bson.M{
"_id": nil,
"total": bson.M{"$sum": "$price"},
"avg": bson.M{"$avg": "$price"},
"max": bson.M{"$max": "$price"},
"min": bson.M{"$min": "$price"},
}},
},
}
facetPipeline = db.AddFacet(facetPipeline, facets)
facetResult, err := db.Aggregate("products", facetPipeline)// Simple count aggregation
count, err := db.AggregateCount("orders", db.WithMatch(bson.M{"status": "completed"}))
// Group by aggregation
groups, err := db.AggregateGroupBy("orders", "$category", db.WithMatch(bson.M{}))
// Distinct values
distinct, err := db.AggregateDistinct("orders",
db.WithMatch(bson.M{}),
db.WithGroupField("status"))All aggregation pipelines are automatically validated and sanitized:
- ✅ Stage Validation: Only safe aggregation stages are allowed
- ✅ Operator Filtering: Dangerous operators like
$where,$functionare blocked - ✅ Input Sanitization: All inputs are sanitized to prevent injection
- ✅ Pipeline Depth Limiting: Prevents deeply nested attack pipelines
- ✅ Collection Name Validation: Ensures valid collection references
// Simple index
result, err := db.CreateIndex("users", bson.M{"email": 1})
// Compound index with bson.D for guaranteed field order
compoundIndex := bson.D{
{Key: "category", Value: 1},
{Key: "price", Value: -1},
}
result, err := db.CreateIndex("products", compoundIndex)
// Text index
textResult, err := db.CreateIndex("articles", bson.M{"title": "text", "content": "text"})
// Geospatial index
geoResult, err := db.CreateIndex("stores", bson.M{"location": "2dsphere"})
// Index with options
indexOptions := options.Index().SetUnique(true).SetSparse(true)
result, err := db.CreateIndexWithOptions("users", bson.M{"email": 1}, indexOptions)// List all indexes
indexes, err := db.ListIndexes("users")
// Drop specific index
dropResult, err := db.DropIndex("users", "email_1")
// Drop all indexes (except _id)
dropAllResult, err := db.DropAllIndexes("users")// Drop collection (requires explicit permission)
dropResult, err := db.DropCollection("old_collection", db.WithAllowBulkDelete(true))
// Drop database (requires explicit permission)
dropResult, err := db.DropDatabase(db.WithAllowBulkDelete(true))// Include specific fields
result, err := db.GetMany("users",
db.WithMatch(query),
db.WithProjectInclude("name", "email"))
// Exclude specific fields
result, err := db.GetMany("users",
db.WithMatch(query),
db.WithProjectExclude("password", "internal_notes"),
db.WithProjectExcludeID())// Single field sorting
result, err := db.GetMany("users",
db.WithMatch(query),
db.WithSortAsc("name"))
result, err := db.GetMany("users",
db.WithMatch(query),
db.WithSortDesc("created_at"))
// Multi-field sorting (use bson.D for guaranteed order)
complexSort := bson.D{
{Key: "category", Value: 1}, // ascending first
{Key: "price", Value: -1}, // descending second
}
result, err := db.GetMany("products",
db.WithMatch(query),
db.WithSort(complexSort))// Page-based pagination
result, err := db.GetMany("users",
db.WithMatch(query),
db.WithPage(2, 25)) // Page 2, 25 items per page
// Offset-based pagination
result, err := db.GetMany("users",
db.WithMatch(query),
db.WithOffsetLimit(50, 25)) // Skip 50, limit 25
// Top N results
result, err := db.GetMany("users",
db.WithMatch(query),
db.WithLimit(10)) // Top 10 results// Custom context with timeout
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
result, err := db.GetMany("users",
db.WithMatch(query),
db.WithContext(ctx))type Config struct {
MongoURI string
ConnectTimeout time.Duration
QueryTimeout time.Duration
MaxPoolSize uint64
MinPoolSize uint64
}
type QueryResult struct {
Data interface{}
Total int64
Page int
Limit int
HasMore bool
ExecutedAt time.Time
}
type OperationResult struct {
Success bool
MatchedCount int64
ModifiedCount int64
UpsertedCount int64
UpsertedID interface{}
DeletedCount int64
ExecutedAt time.Time
}
type AggregationResult struct {
Data []bson.M
Count int
ExecutedAt time.Time
}| Method | Description | Returns |
|---|---|---|
InsertOne(collection, document, ...options) |
Insert single document | *OperationResult, error |
InsertMany(collection, documents, ...options) |
Insert multiple documents | *OperationResult, error |
GetData(collection, ...options) |
Get single document | *mongo.SingleResult, error |
GetMany(collection, ...options) |
Get multiple documents | *QueryResult, error |
SaveData(collection, update, ...options) |
Update/upsert documents | *OperationResult, error |
DeleteOne(collection, ...options) |
Delete single document | *OperationResult, error |
DeleteData(collection, ...options) |
Delete multiple documents | *OperationResult, error |
Count(collection, ...options) |
Count documents | *CountResult, error |
Exists(collection, ...options) |
Check existence | *ExistsResult, error |
| Method | Description | Returns |
|---|---|---|
Aggregate(collection, pipeline, ...options) |
Execute aggregation pipeline | *AggregationResult, error |
AggregateWithCursor(collection, pipeline, ...options) |
Execute with streaming cursor | *mongo.Cursor, error |
AggregateCount(collection, ...options) |
Simple count aggregation | int64, error |
AggregateGroupBy(collection, groupBy, ...options) |
Simple group by | *AggregationResult, error |
AggregateDistinct(collection, ...options) |
Distinct values | *AggregationResult, error |
| Method | Description | Returns |
|---|---|---|
CreateIndex(collection, keys, ...options) |
Create index | *IndexResult, error |
CreateIndexWithOptions(collection, keys, indexOptions, ...options) |
Create index with options | *IndexResult, error |
ListIndexes(collection, ...options) |
List all indexes | []bson.M, error |
DropIndex(collection, indexName, ...options) |
Drop specific index | *DropResult, error |
DropAllIndexes(collection, ...options) |
Drop all indexes | *DropResult, error |
DropCollection(collection, ...options) |
Drop collection | *DropResult, error |
DropDatabase(...options) |
Drop database | *DropResult, error |
| Option | Description |
|---|---|
WithMatch(filter) |
Set query filter |
WithPage(page, limit) |
Set page-based pagination |
WithOffsetLimit(offset, limit) |
Set offset-based pagination |
WithLimit(limit) |
Set result limit |
WithSortAsc(field) |
Sort ascending by field |
WithSortDesc(field) |
Sort descending by field |
WithSort(sortDoc) |
Custom sort document |
WithProjectInclude(fields...) |
Include specific fields |
WithProjectExclude(fields...) |
Exclude specific fields |
WithProjectExcludeID() |
Exclude _id field |
WithUpsert(bool) |
Enable/disable upsert |
WithAllowBulkDelete(bool) |
Enable/disable bulk delete |
WithGroupField(field) |
Set aggregation group field |
WithContext(ctx) |
Set custom context |
Check the examples/ directory for comprehensive usage examples:
basic_usage/main.go- Basic CRUD operations and security featuresadvanced_usage/main.go- Complex queries, bulk operations, and performance optimizationaggregation_examples/main.go- Comprehensive aggregation pipeline examples with all 25+ stagesindex_management/main.go- Complete index management examplessorting_test/main.go- Multi-field sorting examples and best practices
# Basic usage example
go run examples/basic_usage/main.go
# Advanced usage example
go run examples/advanced_usage/main.go
# Comprehensive aggregation examples
go run examples/aggregation_examples/main.go
# Index management examples
go run examples/index_management/main.go
# Sorting examples
go run examples/sorting_test/main.goRun the comprehensive test suite:
# Run all tests
go test ./controller -v
# Run tests with coverage
go test ./controller -v -cover
# Run specific tests
go test ./controller -v -run TestInsertOne
# Run benchmarks
go test ./controller -v -bench=.- MongoDB instance running on
localhost:27017or setMONGODB_TEST_URI - Ensure test database is clean (tests use
simongodm_test_db)
The test suite covers:
- ✅ All CRUD operations
- ✅ Complete aggregation pipeline testing (25+ stages)
- ✅ All pipeline builder methods
- ✅ Index management operations
- ✅ Geospatial functionality
- ✅ Security validation scenarios
- ✅ Error handling edge cases
- ✅ Pagination and sorting
- ✅ Concurrent operations
- ✅ Context usage and timeouts
- ✅ Performance benchmarks
SimonGODM is designed for high performance:
- Connection Pooling: Intelligent connection management with configurable pool sizes
- Query Optimization: Automatic query optimization and validation
- Projection Support: Fetch only needed fields to reduce bandwidth
- Index-Friendly: Designed to work optimally with MongoDB indexes
- Minimal Overhead: Lightweight wrapper around the official MongoDB driver
- Streaming Support: Cursor-based streaming for large result sets
BenchmarkInsertOne-8 1000 1.2ms per operation
BenchmarkGetData-8 5000 0.3ms per operation
BenchmarkGetMany-8 2000 0.8ms per operation
BenchmarkAggregate-8 500 2.1ms per operation
BenchmarkComplexPipeline-8 200 5.2ms per operation- Go 1.21 or higher
- MongoDB 4.0 or higher
- Git
# Clone the repository
git clone https://github.com/RezaArani/simongodm.git
cd simongodm
# Install dependencies
go mod tidy
# Run tests
go test ./controller -v
# Run examples
go run examples/basic_usage/main.gosimongodm/
├── controller/ # Core ODM implementation
│ ├── config.go # Configuration and connection management
│ ├── datatypes.go # Data structures and error types
│ ├── simongodm.go # Main CRUD and aggregation operations
│ ├── options.go # Query options and builders
│ ├── security.go # Security validation and sanitization
│ └── simongodm_test.go # Comprehensive test suite
├── examples/ # Usage examples
│ ├── basic_usage/ # Basic operations demo
│ ├── advanced_usage/ # Advanced features demo
│ ├── aggregation_examples/ # Complete aggregation examples
│ ├── index_management/ # Index management examples
│ └── sorting_test/ # Sorting examples
├── go.mod # Go module definition
├── go.sum # Dependency checksums
└── README.md # This file
We welcome contributions! Please see our contributing guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Add tests for your changes
- Ensure all tests pass (
go test ./controller -v) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow standard Go formatting (
go fmt) - Add comments for exported functions
- Include tests for new features
- Maintain security-first approach
- Update documentation for new features
Security is our top priority. SimonGODM includes:
- Input Validation: All inputs are validated before processing
- Query Sanitization: Automatic removal of dangerous operators
- NoSQL Injection Protection: Built-in protection against NoSQL injection attacks
- Pipeline Validation: Comprehensive aggregation pipeline security
- Safe Defaults: Secure configuration out of the box
- Context Support: Proper timeout and cancellation handling
Please report security vulnerabilities to the project maintainers instead of using public issues.
This project is licensed under the MIT License - see the LICENSE file for details.
- MongoDB Go Driver - Official MongoDB driver for Go
- MongoDB - The database that makes this ODM possible
- All contributors who help make this project better
- Repository: https://github.com/RezaArani/simongodm
- Issues: https://github.com/RezaArani/simongodm/issues
- MongoDB Documentation: https://docs.mongodb.com/
- Go Documentation: https://golang.org/doc/
Made with ❤️ for the Go and MongoDB community
SimonGODM v.1.0.0 - Because database operations should be simple, secure, and fast.