The Mongo ODM for Go
mgm
makes Mongo search and aggregation super easy to do in Golang.mgm
predefines all Mongo operators and keys, so you don't have to hardcode them yourself.mgm
wraps the official Mongo Go Driver.go get github.com/kamva/mgm/v3
To get started, import the mgm
package and setup the default config:
import (
"github.com/kamva/mgm/v3"
"go.mongodb.org/mongo-driver/mongo/options"
)
func init() {
// Setup the mgm default config
err := mgm.SetDefaultConfig(nil, "mgm_lab", options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
}
Define your model:
type Book struct {
// DefaultModel adds _id, created_at and updated_at fields to the Model.
mgm.DefaultModel `bson:",inline"`
Name string `json:"name" bson:"name"`
Pages int `json:"pages" bson:"pages"`
}
func NewBook(name string, pages int) *Book {
return &Book{
Name: name,
Pages: pages,
}
}
Insert new document:
book := NewBook("Pride and Prejudice", 345)
// Make sure to pass the model by reference (to update the model's "updated_at", "created_at" and "id" fields by mgm).
err := mgm.Coll(book).Create(book)
Find one document
// Get the document's collection
book := &Book{}
coll := mgm.Coll(book)
// Find and decode the doc to a book model.
_ = coll.FindByID("5e0518aa8f1a52b0b9410ee3", book)
// Get the first doc of the collection
_ = coll.First(bson.M{}, book)
// Get the first doc of a collection using a filter
_ = coll.First(bson.M{"pages":400}, book)
Update a document
// Find your book
book := findMyFavoriteBook()
// and update it
book.Name = "Moulin Rouge!"
err := mgm.Coll(book).Update(book)
Delete a document
// Just find and delete your document
err := mgm.Coll(book).Delete(book)
Find and decode a result:
result := []Book{}
err := mgm.Coll(&Book{}).SimpleFind(&result, bson.M{"pages": bson.M{operator.Gt: 24}})
Each model by default (by using DefaultModel
struct) has
the following fields:
_id
: The document ID.
created_at
: The creation date of a doc. When saving a new doc, this is automatically populated by the Creating
hook.
updated_at
: The last updated date of a doc. When saving a doc, this is automatically populated by the Saving
hook.
You can even implement your own default model to customize its fields.
Each model has the following hooks:
Creating
: Called when creating a new model.
Signature : Creating(context.Context) error
Created
: Called after a new model is created.
Signature : Created(context.Context) error
Updating
: Called when updating model.
Signature : Updating(context.Context) error
Updated
: Called after a model is updated.
Signature : Updated(ctx context.Context, result *mongo.UpdateResult) error
Saving
: Called when creating or updating a model.
Signature : Saving(context.Context) error
Saved
: Called after a model is created or updated.
Signature: Saved(context.Context) error
Deleting
: Called when deleting a model.
Signature: Deleting(context.Context) error
Deleted
: Called after a model is deleted.
Signature: Deleted(ctx context.Context, result *mongo.DeleteResult) error
Notes about hooks:
Creating
and Saving
hooks, so if you want to define those hooks yourself, remember to invoke the DefaultModel
hooks from your own hooks.Create
& CreateWithCtx
Update
& UpdateWithCtx
Delete
& DeleteWithCtx
Example:
func (model *Book) Creating(ctx context.Context) error {
// Call the DefaultModel Creating hook
if err := model.DefaultModel.Creating(ctx); err!=nil {
return err
}
// We can validate the fields of a model and return an error to prevent a document's insertion.
if model.Pages < 1 {
return errors.New("book must have at least one page")
}
return nil
}
The mgm
default configuration has a context timeout:
func init() {
_ = mgm.SetDefaultConfig(&mgm.Config{CtxTimeout:12 * time.Second}, "mgm_lab", options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
}
// To get the context, just call the Ctx() method, assign it to a variable
ctx := mgm.Ctx()
// and use it
coll := mgm.Coll(&Book{})
coll.FindOne(ctx, bson.M{})
// Or invoke Ctx() and use it directly
coll.FindOne(mgm.Ctx(), bson.M{})
Get a model's collection:
coll := mgm.Coll(&Book{})
// Do something with the collection
mgm
automatically detects the name of a model's collection:
book := Book{}
// Print your model's collection name.
collName := mgm.CollName(&book)
fmt.Println(collName) // output: books
You can also set a custom collection name for your model by implementing the CollectionNameGetter
interface:
func (model *Book) CollectionName() string {
return "my_books"
}
// mgm returns the "my_books" collection
coll := mgm.Coll(&Book{})
Get a collection by its name (without needing to define a model for it):
coll := mgm.CollectionByName("my_coll")
// Do Aggregation, etc. with the collection
Customize the model db by implementing the CollectionGetter
interface:
func (model *Book) Collection() *mgm.Collection {
// Get default connection client
_, client, _, err := mgm.DefaultConfigs()
if err != nil {
panic(err)
}
db := client.Database("another_db")
return mgm.NewCollection(db, "my_collection")
}
Or return a model's collection from another connection:
func (model *Book) Collection() *mgm.Collection {
// Create new client
client, err := mgm.NewClient(options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
if err != nil {
panic(err)
}
// Get the model's db
db := client.Database("my_second_db")
// return the model's custom collection
return mgm.NewCollection(db, "my_collection")
}
While we can use Mongo Go Driver Aggregate features, mgm
also
provides simpler methods to perform aggregations:
Run an aggregation and decode the result:
authorCollName := mgm.Coll(&Author{}).Name()
result := []Book{}
// Lookup with just a single line of code
_ := mgm.Coll(&Book{}).SimpleAggregate(&result, builder.Lookup(authorCollName, "auth_id", "_id", "author"))
// Multi stage (mix of mgm builders and raw stages)
_ := mgm.Coll(&Book{}).SimpleAggregate(&result,
builder.Lookup(authorCollName, "auth_id", "_id", "author"),
M{operator.Project: M{"pages": 0}},
)
// Do something with result...
Do aggregations using the mongo Aggregation method:
import (
"github.com/kamva/mgm/v3"
"github.com/kamva/mgm/v3/builder"
"github.com/kamva/mgm/v3/field"
. "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// The Author model collection
authorColl := mgm.Coll(&Author{})
cur, err := mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), A{
// The S function accepts operators as parameters and returns a bson.M type.
builder.S(builder.Lookup(authorColl.Name(), "author_id", field.Id, "author")),
})
A more complex example and mixes with mongo raw pipelines:
import (
"github.com/kamva/mgm/v3"
"github.com/kamva/mgm/v3/builder"
"github.com/kamva/mgm/v3/field"
"github.com/kamva/mgm/v3/operator"
. "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Author model collection
authorColl := mgm.Coll(&Author{})
_, err := mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), A{
// S function get operators and return bson.M type.
builder.S(builder.Lookup(authorColl.Name(), "author_id", field.Id, "author")),
builder.S(builder.Group("pages", M{"books": M{operator.Push: M{"name": "$name", "author": "$author"}}})),
M{operator.Unwind: "$books"},
})
if err != nil {
panic(err)
}
mgm.Transaction()
function, e.g:
d := &Doc{Name: "Mehran", Age: 10}
err := mgm.Transaction(func(session mongo.Session, sc mongo.SessionContext) error {
// do not forget to pass the session's context to the collection methods. err := mgm.Coll(d).CreateWithCtx(sc, d)
if err != nil {
return err
}
return session.CommitTransaction(sc)
})
- To run a transaction with your own context, use the `mgm.TransactionWithCtx()` method.
- To run a transaction on another connection, use the `mgm.TransactionWithClient()` method.
-----------------
## Other Mongo Go Models Packages
**We implemented these packages to simplify queries and aggregations in mongo**
`builder`: simplify mongo queries and aggregations.
`operator` : contains mongo operators as predefined variables.
(e.g `Eq = "$eq"` , `Gt = "$gt"`)
`field` : contains mongo fields used in aggregations and ... as predefined variable.
(e.g `LocalField = "localField"`, `ForeignField = "foreignField"`)
Example:
```go
import (
"github.com/kamva/mgm/v3"
f "github.com/kamva/mgm/v3/field"
o "github.com/kamva/mgm/v3/operator"
"go.mongodb.org/mongo-driver/bson"
)
// Instead of hard-coding mongo operators and fields
_, _ = mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), bson.A{
bson.M{"$count": ""},
bson.M{"$project": bson.M{"_id": 0}},
})
// Use the predefined operators and pipeline fields.
_, _ = mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), bson.A{
bson.M{o.Count: ""},
bson.M{o.Project: bson.M{f.Id: 0}},
})
New features can be requested and bugs can be reported on Github issue tracker.
git clone https://github.com/<your_username>/mgm && cd mgm
)git checkout -b my-new-feature
)git add .
)git commit -m 'Add some feature'
)git push origin my-new-feature
)Mongo Go Models is released under the Apache License