Generic, type-safe MongoDB repository for Go. Wraps the official MongoDB driver with a clean API that handles collections, indexes, timestamps, and pagination out of the box.
go get github.com/DmitriyHellyeah/mongokittype User struct {
mongokit.BaseField `bson:",inline"`
Name string `bson:"name"`
Email string `bson:"email"`
}
func (*User) CollectionName() string { return "users" }BaseField gives you _id, createdAt, and updatedAt fields automatically:
BeforeInsert- called on everyInsertOne/InsertMany. Generates_idif empty, setscreatedAtandupdatedAtto current time.BeforeUpdate- called on update methods when passing a struct (notbson.M). UpdatesupdatedAtto current time.
Note: when updating with
bson.M{"$set": ...}orbson.M{"$inc": ...}, timestamps are not updated automatically. Use a struct to get auto-timestamps, or addupdatedAtto your operator manually.
db, err := mongokit.Connect(ctx, "mongodb://localhost:27017", "myapp")
if err != nil {
log.Fatal(err)
}
users, err := mongokit.NewRepository[*User](ctx, db)
if err != nil {
log.Fatal(err)
}// Insert - ID and timestamps set automatically
user, err := users.InsertOne(ctx, &User{Name: "John", Email: "john@example.com"})
// Find
user, err := users.FindByID(ctx, id)
user, err := users.FindOne(ctx, bson.M{"email": "john@example.com"})
all, err := users.FindDecoded(ctx, bson.M{"status": "active"})
// Update
users.UpdateByID(ctx, id, bson.M{"$set": bson.M{"name": "Jane"}})
// Delete
users.DeleteByID(ctx, id)Define indexes on the model - they are created automatically when the repository is initialized:
func (*User) Indexes() []mongo.IndexModel {
return mongokit.BuildIndexes(
mongokit.UniqueIndexes("email"),
mongokit.NonUniqueIndexes("status", "-createdAt"),
)
}String shortcuts: "-" prefix for descending, comma for composite keys (e.g. "userId,-createdAt").
You can also use standard mongo.IndexModel directly for advanced cases (TTL, partial filters, etc.):
func (*Session) Indexes() []mongo.IndexModel {
return mongokit.BuildIndexes(
mongokit.NonUniqueIndexes("userId"),
[]mongo.IndexModel{
{
Keys: bson.D{{Key: "expiresAt", Value: 1}},
Options: options.Index().SetExpireAfterSeconds(0),
},
},
)
}Offset:
items, total, err := users.FindPaginatedWithTotal(ctx, bson.M{}, 1, 20)Cursor-based:
page, err := users.FindCursorPaginated(ctx,
bson.M{},
&mongokit.CursorPagination{Limit: 20, Direction: mongokit.CursorNext},
bson.D{{Key: "_id", Value: 1}},
func(u *User) (string, error) { return u.ID.Hex(), nil },
)
// page.Items, page.HasNext, page.HasPrev, page.NextCursor, page.PrevCursorExtend the built-in repository with your own methods:
type UserRepo struct {
*mongokit.Repository[*User]
}
func (r *UserRepo) FindActiveByEmail(ctx context.Context, email string) (*User, error) {
return r.FindOne(ctx, bson.M{"email": email, "status": "active"})
}// Map mongo "not found" to your domain error
user, err := users.FindByID(ctx, id)
err = mongokit.MapNotFoundErr(err, ErrUserNotFound)Sentinel errors: ErrNilFilter, ErrNilUpdate, ErrNilPipeline, ErrNilID, ErrEmptySlice, ErrEmptyCollectionName, ErrUnsupportedUpdateType.
myapp/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── database/
│ │ ├── database.go # Database struct, Initialize()
│ │ ├── model/
│ │ │ ├── user.go # model + CollectionName + Indexes
│ │ │ ├── task.go
│ │ │ └── session.go
│ │ └── repository/
│ │ ├── user.go # custom UserRepo with domain methods
│ │ └── task.go
│ └── service/
│ └── user.go # business logic
└── go.mod
// internal/database/model/user.go
type User struct {
mongokit.BaseField `bson:",inline"`
Name string `bson:"name"`
Email string `bson:"email"`
Status string `bson:"status"`
}
func (*User) CollectionName() string { return "users" }
func (*User) Indexes() []mongo.IndexModel {
return mongokit.UniqueIndexes("email")
}// internal/database/repository/user.go
type UserRepo struct {
*mongokit.Repository[*model.User]
}
func NewUserRepo(ctx context.Context, db *mongo.Database) (*UserRepo, error) {
repo, err := mongokit.NewRepository[*model.User](ctx, db)
if err != nil {
return nil, err
}
return &UserRepo{repo}, nil
}
func (r *UserRepo) FindActiveByEmail(ctx context.Context, email string) (*model.User, error) {
return r.FindOne(ctx, bson.M{"email": email, "status": "active"})
}// internal/database/database.go
type Database struct {
Users *repository.UserRepo
Tasks *mongokit.Repository[*model.Task] // no custom methods needed
}
func Initialize(ctx context.Context, uri, dbName string) (*Database, error) {
db, err := mongokit.Connect(ctx, uri, dbName)
if err != nil {
return nil, err
}
users, err := repository.NewUserRepo(ctx, db)
if err != nil {
return nil, err
}
tasks, err := mongokit.NewRepository[*model.Task](ctx, db)
if err != nil {
return nil, err
}
return &Database{Users: users, Tasks: tasks}, nil
}// cmd/server/main.go
func main() {
ctx := context.Background()
db, err := database.Initialize(ctx, os.Getenv("MONGO_URI"), "myapp")
if err != nil {
log.Fatal(err)
}
user, err := db.Users.FindActiveByEmail(ctx, "john@example.com")
task, err := db.Tasks.FindByID(ctx, taskID)
}See examples/ for runnable demos:
basic- CRUD, timestamps, MapNotFoundErrindexes- all index variantstransactions- transaction with rollbackpagination- offset + cursorcustom-repo- extended repository
MIT