Open-source project
qiniu/qmgo avatar
qiniu/qmgo

qiniu/qmgo: the Go MongoDB driver with the mgo chain call API on top of the official driver

Qmgo - The Go driver for MongoDB. It‘s based on official mongo-go-driver but easier to use like Mgo.

1,355 stars151 forksGoApache-2.0

At a glance

What is it?
Qmgo is an Apache-2.0 licensed Go driver for MongoDB that wraps the official mongo-go-driver and restores the chained query style of the older mgo library, with transactions, hooks, validation tags and aggregation pipelines. Its own documentation is explicit about the design goal, minimal code changes when migrating from mgo, and the module pins an old Go version and a specific mongo-driver release.
Who is it for?
Adopt qmgo if you are migrating Go code from the archived mgo library and want the chain call style back without giving up the official driver's features, because that is the exact case the project is built for and the wrappers are thin enough to reason about. Do not adopt it for a new project that has no mgo history, since the official driver is the better default and qmgo adds a layer you do not need.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 125 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why there is another Go driver

Qmgo is a Go driver for MongoDB, and the description gives the reason for its existence in one sentence. It is based on the MongoDB official driver, but easier to use like mgo, with the chain call as the example.

The two claims in that sentence are the whole design. The first is inheritance, because Qmgo sits on top of the official `mongo-go-driver` rather than reimplementing the wire protocol, which means the connection handling, the BSON encoding and the server feature detection are the official ones. The second is presentation, because the query API is the one that mgo users already have in their fingers.

That combination is aimed at a specific migration. The README says Qmgo is the first choice for migrating from mgo to the new MongoDB driver with minimal code changes, and it also says Qmgo allows users to use the new features of MongoDB in a more elegant way. The older `go-mgo` project is unmaintained, and its API was pleasant enough that a lot of code in the wild was written against it, so a driver that keeps the shape of that API while carrying the current driver underneath removes a category of work.

The requirements are stated plainly, Go 1.10 and above and MongoDB 2.6 and above. The features list covers CRUD with all the officially supported options, sort, limit, count, select and distinct, transactions, hooks, automatically default and custom fields, predefine operator keys, aggregate, index operations and cursors, validation tags, and a plugin mechanism.

The repository is one flat package with subpackages for the parts that are optional, `field/`, `hook/`, `middleware/`, `operator/`, `options/` and `validator/`, and every file has a matching test file beside it.

Two ways to open a connection, and the second one is better

There are two ways to initialise, and the difference is worth understanding because the whole API depends on it.

The first creates a client and walks down to the collection:

go
import (
    "context"
  
    "github.com/qiniu/qmgo"
)

ctx := context.Background()
client, err := qmgo.NewClient(ctx, &qmgo.Config{Uri: "mongodb://localhost:27017"})
db := client.Database("class")
coll := db.Collection("user")

The second binds the database and collection in the configuration and hands you a single object, and the documentation recommends it if your connection points at a fixed database and collection. All operations are then based on `cli`:

go
cli, err := qmgo.Open(ctx, &qmgo.Config{Uri: "mongodb://localhost:27017", Database: "class", Coll: "user"})

And the note that follows explains the ergonomics, that the following examples are based on `cli`, and that if you use the first way for initialisation you replace `cli` with `client`, `db` or `coll`.

So the two initialisers are not different APIs, they are the same API with a different entry point, and the choice is about how much of the path you carry around in your code. The second form is the mgo shape, where you hold one handle, which is why it is the recommended one.

One habit the documentation insists on is closing the client, with a deferred call to `Close` after instantiating, and a panic on error, because the client owns a connection pool and leaking it is the classic mgo era mistake.

The chain call is the point

Every query in the library is a chain that ends in a terminal operation, and the operations are the familiar ones.

go
// find all, sort and limit
batch := []UserInfo{}
cli.Find(ctx, bson.M{"age": 6}).Sort("weight").Limit(7).All(&batch)

A single document ends in `One`, and there is a `Count` for the number of matches, and `Select` for a projection:

go
err = cli.Find(ctx, bson.M{"age": 10}).Select(bson.M{"age": 1}).One(&one)

Updates follow the same shape, with `UpdateOne` and `UpdateAll` taking a filter and a document with the update operators:

go
err := cli.UpdateOne(ctx, bson.M{"name": "d4"}, bson.M{"$set": bson.M{"age": 7}})

result, err := cli.UpdateAll(ctx, bson.M{"age": 6}, bson.M{"$set": bson.M{"age": 10}})

Removes are `Remove` with a filter, and inserts are `InsertOne` and `InsertMany`.

The aggregation API is a `Pipeline` of stages, and the result is fetched with `All` like any other query:

go
matchStage := bson.D{{"$match", []bson.E{{"weight", bson.D{{"$gt", 30}}}}}}
groupStage := bson.D{{"$group", bson.D{{"_id", "$name"}, {"total", bson.D{{"$sum", "$age"}}}}}}
var showsWithInfo []bson.M
err = cli.Aggregate(context.Background(), Pipeline{matchStage, groupStage}).All(&showsWithInfo)

Two details are worth flagging. The official driver options are all still available at connection time, so you can pass a pool monitor, for example, with `options.Client().SetPoolMonitor(poolMonitor)` and hand it to `Open` as an extra argument. And the operator keys can be predefined constants instead of string literals, with `operator.Match`, `operator.Gt`, `operator.Group` and `operator.Sum` from the `operator` package, which removes a class of typo from stage definitions.

Transactions, and the one rule that breaks them

The transaction support is described as super simple and powerful, with features like timeout and retry, and the API is a callback.

go
callback := func(sessCtx context.Context) (interface{}, error) {
    // Important: make sure the sessCtx used in every operation in the whole transaction
    if _, err := cli.InsertOne(sessCtx, bson.D{{"abc", int32(1)}}); err != nil {
        return nil, err
    }
    if _, err := cli.InsertOne(sessCtx, bson.D{{"xyz", int32(999)}}); err != nil {
        return nil, err
    }
    return nil, nil
}
result, err = cli.DoTransaction(ctx, callback)

The important line is the comment inside the callback. Every operation in the transaction has to use the session context that is passed in, not the outer one, because the session context is what carries the transaction identity to the server. Use the outer context by accident and the writes go outside the transaction, and they will often succeed, which is the worst kind of bug.

That single requirement is the price of the callback design, and it is a price worth paying for the retry and timeout handling that the callback gives you, since a transaction with an automatic retry has to be able to re-run its body, and a body that captured the wrong context cannot be replayed safely.

Hooks and validation, and the packages around them

Hooks are how you attach behaviour to a document type, and the mechanism is a method convention on your own struct.

go
type User struct {
    Name         string    `bson:"name"`
    Age          int       `bson:"age"`
}
func (u *User) BeforeInsert(ctx context.Context) error {
    fmt.Println("before insert called")
    return nil
}
func (u *User) AfterInsert(ctx context.Context) error {
    fmt.Println("after insert called")
    return nil
}

So a type can implement `BeforeInsert` and `AfterInsert`, and the hook package dispatches on the presence of those methods. The same pattern generalises to the other lifecycle points, and it means the behaviour lives with the document type rather than in a registry somewhere else, which is the design that survives a refactor.

Validation tags are the other extension point, and they are built on the `go-playground/validator` library, which is what the dependency list shows. The `validator/` package in the repository is the bridge, and the feature list calls it out separately from the hooks.

The `field/` package covers the automatically default and custom fields feature, and the `middleware/` package is there for the plugin mechanism. The plugin idea, in a driver, is the ability to intercept or extend behaviour at the client or collection level, and separating it into its own package keeps the core small.

The dependency floor is the thing to check first

The `go.mod` is short and it is the fastest way to understand where the project stands.

go
module github.com/qiniu/qmgo

go 1.16

require (
	github.com/go-playground/validator/v10 v10.4.1
	github.com/stretchr/testify v1.6.1
	go.mongodb.org/mongo-driver v1.17.1
)

Three dependencies, of which one is the official driver, one is the validation library that the validation tags are built on, and one is the test framework. The test framework being a direct requirement rather than a dev only one is a small packaging smell, but it is the kind of thing you find out about when you check the module graph.

The more important line is `go 1.16`. The README's stated requirement is Go 1.10 and above, but the module declares 1.16, and the driver is pinned at mongo-driver 1.17.1, which is a specific release of the official driver rather than the latest one. For a wrapper library, pinning is the right call, since the wrapper's behaviour is tied to the API of the version it wraps, but it means the two versions have to be read together. A project that is on a much newer mongo-driver is not going to get the newest driver features through Qmgo without pulling a newer Qmgo.

The release history is slow and steady, with v1.1.8 on 2023-07-05, v1.1.9 on 2024-10-31 and v1.1.10 on 2025-07-05, and the last push to the repository on 2026-06-20. The repository is not archived, and the README exists in English and in Chinese, with a Chinese original alongside the English version.

Where Qmgo is the wrong choice

The first wrong case is a greenfield project with no mgo history. If you are starting a new Go service that talks to MongoDB, the official driver is the default choice, it is the one with the current feature set, and the one with the current documentation. Qmgo's value is in the ergonomics of a familiar API, and that is worth nothing if the API is not familiar to the team writing the code.

The second wrong case is a project that needs a feature the pinned driver does not have. Since Qmgo is a wrapper around mongo-driver 1.17.1, a server feature introduced after that release may be reachable only by dropping down to the underlying driver, and at that point you are maintaining two layers.

The third is a codebase that has already standardised on the official driver. Adding Qmgo for the chain syntax means two query styles in one repository, and the `cli` versus `client`, `db`, `coll` choice in the documentation exists precisely because both are supported.

The fourth is a team that has been bitten by the transaction context rule and decides that is too sharp an edge. The requirement that every operation inside a transaction use the session context is easy to get wrong, and getting it wrong produces writes that appear to succeed outside the transaction. A codebase that prefers the official driver's session handling has a better argument for staying with it.

What Qmgo does well is narrow and real. It gives mgo users a path to a maintained driver, it keeps the official driver's options available at connection time, and it adds the pieces that a chain style API needs, the terminal operations, the operator constants, the hooks and the transaction callback.

Editorial conclusion

Adopt qmgo if you are migrating Go code from the archived mgo library and want the chain call style back without giving up the official driver's features, because that is the exact case the project is built for and the wrappers are thin enough to reason about. Do not adopt it for a new project that has no mgo history, since the official driver is the better default and qmgo adds a layer you do not need. Verify first that the mongo-driver version your project already uses is compatible with the one qmgo pins, that your Go toolchain satisfies the module's declared version, and that the collection helpers you rely on are the ones qmgo implements rather than the ones you would call on the official driver directly.

Frequently asked questions

What is Qmgo and who is it for?

Qmgo is a Go driver for MongoDB built on the official mongo-go-driver, with the chain call API of the older mgo library. The README describes it as the first choice for migrating from mgo to the new MongoDB driver with minimal code changes, and it requires Go 1.10 and above and MongoDB 2.6 and above.

How do I create a Qmgo connection?

Either with `qmgo.NewClient(ctx, &qmgo.Config{Uri: ...})` and then `client.Database(...)` and `db.Collection(...)`, or with `qmgo.Open(ctx, &qmgo.Config{Uri: ..., Database: ..., Coll: ...})`, which is the recommended form when the database and collection are fixed. The documentation asks you to defer a call to `Close` after instantiating the client.

How do transactions work in Qmgo?

Through `cli.DoTransaction(ctx, callback)`, where the callback receives a session context and returns an error to abort. The documentation stresses that every operation in the whole transaction must use that session context, since using the outer context sends the writes outside the transaction.

Which MongoDB driver version does Qmgo use?

The module requires `go.mongodb.org/mongo-driver v1.17.1` and declares `go 1.16`, with `github.com/go-playground/validator/v10` for the validation tag support and `github.com/stretchr/testify` for the tests. The pinned driver version means a newer official driver release is not reachable through Qmgo without a newer Qmgo.

What hooks does Qmgo support?

Hooks are methods on your own document type, for example a `BeforeInsert(ctx)` or `AfterInsert(ctx)` method on the struct, with the `hook/` package dispatching on the presence of those methods, so the behaviour lives with the type rather than in a central registry.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. qiniu/qmgo on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/qiniu-qmgo.svg)](https://hysenlabs.com/projects/qiniu-qmgo)

Was this useful?