# Mongo Go Models and the thin MongoDB ODM layer for Go

> Kamva/mgm wraps the official Mongo driver v2 with struct models, lifecycle hooks and predefined operators. The convenience is real, but the cost sits in hook ordering, the driver v2 upgrade and a README that stops well short of transactions and indexes.

**Kamva/mgm** — Mongo Go Models (mgm) is a fast and simple MongoDB ODM for Go (based on official Mongo Go Driver)

- Repository: https://github.com/Kamva/mgm
- Stars: 764 · Forks: 62
- Language: Go
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/kamva-mgm

## A facade over the official driver rather than a second driver

Kamva/mgm describes itself as "The Mongo ODM for Go", and the important word in that sentence is what it wraps. The README states that mgm "wraps the official Mongo Go Driver", and go.mod lists go.mongodb.org/mongo-driver/v2 v2.1.0 as a direct dependency. The connection string parsing, the wire protocol, BSON encoding and connection pooling therefore stay inside the driver's own code, and mgm sits on top as a convenience layer rather than as a parallel implementation of MongoDB access.

The top-level layout is consistent with that idea. collection.go, model.go, hooks.go, operation.go, field.go, transaction.go and connection.go are the main files, with builder/, field/, operator/ and internal/ as subpackages, plus examples/aggregate/ and examples/crud/. Nothing here attempts to replace the driver. What mgm adds is the layer that sits between a Go struct and a MongoDB document: a model interface, a collection type, a hook set and a table of predefined query operators.

For a Go developer who wants a document round trip and a place to hang validation or timestamps, that is a reasonable amount of abstraction. For a developer who needs control over session handling, index creation or aggregation pipeline details, the underlying driver remains the thing you will end up reading.

## Installing mgm v3 means changing your import path

The module carries a major version suffix, so the package path is github.com/kamva/mgm/v3 and the install command is short:

```bash
go get github.com/kamva/mgm/v3
```

Two version constraints come with it. The README lists Go 1.22 or higher, and MongoDB 4.0 and higher, the latter required by the official driver v2. go.mod agrees on the language side with a go 1.22 directive.

Because the module sits at v3 and the driver sits at v2, the import block in your service carries two version suffixes at once:

```go
import (
   "github.com/kamva/mgm/v3"
   "go.mongodb.org/mongo-driver/v2/mongo/options"
)
```

That double suffix is the first practical signal that this library tracks the driver's breaking changes rather than insulating you from them. The README dedicates a section to the upgrade, listing import path changes, the move from primitive.ObjectID to bson.ObjectID, a changed TransactionFunc signature and the switch to the driver's builder API for collection and operation options.

## One default config, one collection name inferred from the struct

Configuration is global rather than per client. The README sets it up once in an init function, passing a nil config first and later showing a variant that sets a context timeout:

```go
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{})
```

The second argument is the database name, and the third is the driver's own client options value built with ApplyURI. A fixed timeout applied through mgm.Ctx() is convenient, but it applies globally to every call that uses that context, which matters when one collection in your service needs a slower query than another.

Collection naming is derived rather than declared. The README shows that a Book model resolves to a books collection:

```go
book := Book{}

// Print your model's collection name.
collName := mgm.CollName(&book)
fmt.Println(collName) // output: books
```

The pluralisation comes from github.com/jinzhu/inflection, which go.mod lists as a direct dependency. The consequence for a reviewer is that a renamed Go type changes the collection name, and therefore the data location, unless the mapping is verified.

## Hook ordering decides whether your timestamps get written

mgm defines eight model hooks: Creating, Created, Updating, Updated, Saving, Saved, Deleting and Deleted. The ones that touch the embedded DefaultModel are Creating, which populates created_at, and Saving, which populates updated_at on both creates and updates. A model also picks up _id from the same embedded struct.

The trap is inheritance. The README warns that each model by default uses the Creating and Saving hooks, so a custom hook has to invoke the DefaultModel hook or those fields stop being filled. The validation pattern looks like this:

```go
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 failure mode is quiet. If you omit that first call, the insert still succeeds, and the document simply arrives without a creation timestamp. Nothing in the signature forces the delegation, so this is a convention the code review has to enforce. Note also that only Create, Update and Delete and their Ctx variants trigger hooks, so a raw driver call or a bulk write performed outside the collection methods bypasses the hook layer entirely.

The document order also matters for updates. Updated receives a *mongo.UpdateResult and Deleted receives a *mongo.DeleteResult, so a hook that needs the matched or deleted count has access to it, while Creating, Saving and their after counterparts only receive a context.

## Predefined operators instead of hand written query keys

One of the stated features is that mgm predefines all Mongo operators and keys, so you do not have to hardcode them yourself. The operator package holds those constants, and a filtered find reads like this:

```go
result := []Book{}

err := mgm.Coll(&Book{}).SimpleFind(&result, bson.M{"pages": bson.M{operator.Gt: 24}})
```

The query itself is still a bson.M document, so mgm does not replace the driver's filter types. What the operator package adds is a named constant where a raw string such as "$gt" would otherwise be written. The benefit is compile time safety on the key name, which matters once filters are assembled from request parameters or configuration rather than from a single literal.

The same style applies to field handling through the field package, and the README lists making Mongo search and aggregation easier as a feature of the library. The repository also carries an examples/aggregate/ directory, which suggests aggregation is a supported path, although the README text for this project does not include a full aggregation example that can be followed line by line.

## CRUD is one line, but the edges of the library are documented unevenly

The everyday operations are compact. Creating a document requires passing the model by reference so that mgm can write back the id and timestamps:

```go
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)
```

Update and Delete follow the same shape, taking the model instance as the argument:

```go
// Find your book
book := findMyFavoriteBook()

// and update it
book.Name = "Moulin Rouge!"
err := mgm.Coll(book).Update(book)
```

```go
// Just find and delete your document
err := mgm.Coll(book).Delete(book)
```

What is documented stops there. There is no section on index creation or index management, no migration or schema version mechanism, no guide to writing a custom validator beyond the hook example, and no walkthrough of transactions. A transaction.go file and a transaction_test.go file exist in the repository, but the only mention of transactions in the README is the changed TransactionFunc signature, which now receives a session pointer and a plain context.Context rather than a session value and a mongo.SessionContext. The library also publishes no GitHub releases, so there is no changelog to consult when you want to know which behaviour changed between tags. A team that needs transactions, index lifecycle or schema evolution is working below the layer mgm covers.

## Upgrade cost when the driver moved from v1 to v2

The most expensive part of adopting this project is not the API surface, it is the driver migration that the project inherits. The README lists four breaking changes for anyone moving from a driver v1 based release: import go.mongodb.org/mongo-driver/v2/... instead of the v1 path, use bson.ObjectID because the bson/primitive package was removed in v2, expect TransactionFunc to receive (session *mongo.Session, sc context.Context), and use the driver's builder API for options such as options.FindOne().SetComment(...), since bare option struct pointers are no longer accepted.

Each of those touches application code rather than library code. Any helper that builds a find options struct by hand has to be rewritten as a builder chain, any code that references primitive.ObjectID has to change type, and any transaction wrapper has to change signature. Because the project has no GitHub releases, the README upgrade section and the UPGRADE.md file at the repository root are the places to look, and neither is a per version history.

On licence terms, the repository is Apache-2.0, which permits use in commercial services and requires that notices be carried with redistribution; that is a reading of the licence text and not legal advice. On maintenance, the last push was on 2026-08-03, so the code has seen recent commits, but with no published releases and no version history beyond the module suffix, treat each tag as a decision point rather than expecting a steady release cadence.

## Conclusion

Adopt mgm when your Go service wants a document round trip, a small hook surface and typed models without hand writing every BSON filter, and you are willing to keep the official driver within reach. Skip it if your team needs documented schema validation, index management or transaction examples, because the README covers none of those and the repository publishes no GitHub releases. Before committing, run the driver v2 import change through an existing service, because primitive.ObjectID became bson.ObjectID and TransactionFunc now receives a session pointer with a plain context.Context, and check that a custom Creating hook still calls the DefaultModel hook so created_at is written.

## FAQ

### What Go and MongoDB versions does Kamva/mgm require?

The README lists Go 1.22 or higher and MongoDB 4.0 and higher, the latter being required by the official driver v2 that mgm now wraps. go.mod declares the module as github.com/kamva/mgm/v3 with a go 1.22 directive.

### How do I install the Kamva/mgm ODM in a Go project?

Run go get github.com/kamva/mgm/v3 and import github.com/kamva/mgm/v3. Because the driver moved to v2, the import block also pulls go.mongodb.org/mongo-driver/v2/mongo/options.

### How does Kamva/mgm populate created_at and updated_at?

Both fields come from the embedded mgm.DefaultModel. created_at is populated by the Creating hook and updated_at by the Saving hook, so a custom hook must call the DefaultModel hook of the same name or the timestamps stop being written.

### What changed in the mongo-driver v2 based release of Kamva/mgm?

Imports move to go.mongodb.org/mongo-driver/v2/..., primitive.ObjectID becomes bson.ObjectID, TransactionFunc receives a session pointer with a plain context.Context, and collection and operation options use the driver's builder API instead of bare option struct pointers.

## Sources

- [Issues](https://github.com/Kamva/mgm/issues)
- [Kamva/mgm on GitHub](https://github.com/Kamva/mgm)
- [License: Apache-2.0](https://github.com/Kamva/mgm/blob/master/LICENSE)
- [README](https://github.com/Kamva/mgm/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kamva-mgm
