README ¶
gomongo
A handy wrapper for working with the standard mongo-go-driver. This package lets you avoid a lot of the bson.M
s and the bson.D
s and
all the other fun things about working with a language that doesn't deal with JSON natively.
- Query Builder
- Repository
- Condition Pipe (deprecated in favor of query builder)
- Aggregate Pipe (deprecated in favor of query builder)
Install
$ go get github.com/clarkmcc/gomongo
Query Builder
This library was originally introduced with condition builders that allowed callers to more easily build queries and pipelines using functions rather than figuring out how to use the bson
types. This however was not an appreciable improvement over the user experience of the bson
package. I've now added a Go template based query builder that allows you to build queries in JSON form and then insert Go template variables dynamically.
Example
JSON
{
"$match": {
"_id": ObjectId("000000000000000000000000")
}
}
BSON
key, err := primitive.ObjectIdFromHex("000000000000000000000000")
if err != nil {
panic(err)
}
q := bson.M{"$match": bson.M{
"_id": key
}}
Query Builder
q, err := qb.Build[bson.M](`{"$match": {"_id": {{ oid .id }}}}`, map[string]any{
"id": "000000000000000000000000",
})
Regular Expressions
You can also use the query builder to write queries using regular expressions.
q, err := qb.Build[bson.M](`{"$match": {"name": {{ regex .name }}}}`, map[string]any{
"name": "/john|jane/i",
})
Custom Template Functions
The template function map is exposed so that the behavior of existing functions like oid
and regex
can be overridden or new functions created
qb.Builtins["foobar"] = func(value reflect.Value) (any, error) {
// Do something...
return nil, nil
}
Repositories
A repository represents a collection of methods that are called and executed on a specific collection, in other words you'll always have one repository per collection. The BaseRepository has a bunch of standard helper methods and is used by embedding it into our own custom repositories.
For example, lets say we want to create a repository that interacts with the user
collection. We'd start by defining a
UserRepository
like this:
type UserRepository struct {
CollectionName string
*database.BaseRepository
}
We'll then make a method to connect our UserRepository to our MongoDB client:
func NewUserRepository(client *mongo.Client) *UserRepository {
r, err := database.NewBaseRepository(client, <DATABASE_NAME>, "user")
if err != nil {
log.Fatalf("initializing base repository: %v", err)
}
return &UserRepository{
BaseRepository: r,
}
}
All of the methods in the BaseRepository
are available to any custom repository, some of these methods are:
- Find - Takes a query and an interface and decodes the response into the interface
- FindCursor - Takes a query and returns the results in the form of a
*mongo.Cursor
- FindOne - Takes a query and an interface and decodes the first matching response into the interface
- FindById - Takes a string
_id
and decodes the first matching response into the interface - FindByIdList - Takes a list of string
_id
and an interface and decodes the response into the interface - FindDistinct - Takes a
fieldName
, a query and an interface and decodes the distinct values for the providedfieldName
into the interface - Aggregate - Takes a pipeline and an
allowDiskUse
bool
and returns a*mongo.Cursor
- Create - Takes a document of type
Document
(interface) and inserts it into the repositories collection with an autogenerated_id
,createdDate
andmodifiedDate
- CreateMany - Does the same as above but accepts a slice of type
Document
- Update - Take a
filter
(query) and an update document (see https://docs.mongodb.com/manual/reference/operator/update/#id1) and amulti
bool. Ifmulti
istrue
then it will update all documents matching thefilter
, otherwise it will update the first document to match the filter - UpdateById - Takes a string
_id
, and update document, anautoSet
bool
and a[]DocStatus
. If you're using the operators defined here (see https://docs.mongodb.com/manual/reference/operator/update/#id1), then you can setautoSet
tofalse
, otherwise it will automatically take your update document and wrap it inside a MongoDB$set
operator (NOTE: when doing any update, make sure to update themodifiedDate
yourself).[]DocStatus
Has two options,Active
= 1 orInactive
= 0 that you can use to filter (filters on the documentstatus
field). Document statuses provide an alternative method for 'deleting' documents. - UpdateByIdList - Does the same as above but accepts a list of string
_id
to perform the update operation on - DeleteById - Takes a string
_id
and deletes the document. This is discouraged, instead use the UpdateById method and set the status to0
.
Extending the BaseRepository Functionality
The default functionality could be extended and simplified using our previous example of a UserRepository
. FindById
decodes the response into a cursor but it can be extended like this in the UserRepository
:
func (r *UserRepository) FindUserById(ctx context.Context, id string) (*User, error) {
user := User{}
err := r.FindById(ctx, id, &user)
if err != nil {
return nil, fmt.Errorf("running FindUserById: %v", err)
}
return &user, nil
}
In this case the decoding into a struct is handled in a repository and calling this method returns the user that was found:
user, err := FindUserById(ctx, "5c7836b73a8de34c78fec399")
Condition Pipe
The condition pipe allows you to chain query operators together to create a MongoDB query without having to deal with the bson library directly. Here's an example, refer to the source code (documented) for additional operators:
condition := Pipe(
DateLessThanOrEqualTo(Condition{
Key: "endDate",
Value: "2006-01-02T15:04:05.000Z",
}),
DateGreaterThanOrEqualTo(Condition{
Key: "startDate",
Value: "2006-01-02T15:04:05.000Z",
}),
)
This outputs the following:
{
"endDate": {
"$lte": "2006-01-02T15:04:05Z"
},
"startDate": {
"$gte": "2006-01-02T15:04:05Z"
}
}
Note that condition pipes can be put inside aggregate operators such as the Match
operator (see below).
Aggregate Pipe
The aggregate pipe allows you to chain operators together in order to create a MongoDB pipeline without having to deal with the bson library directly. Here's an example, refer to the source code (documented) for additional operators:
pipeline := Pipe(
Match(
Operation(
condition.Pipe(
condition.ObjectIdMatch(condition.Condition{
Key: "_id",
Value: "5c7836b73a8de34c78fec399"}),
condition.EqualTo(condition.Condition{
Key: "status",
Value: 1,
}),
condition.StringStartsWith(condition.Condition{
Key: "model",
Value: "T654",
}),
),
),
),
Project(
Operation{
"name": 1,
"make": 1,
"model": 1,
},
),
)
This outputs the following:
[
{
"$match": {
"_id": {
"$eq": "5c7836b73a8de34c78fec399"
},
"model": {
"$regex": {
"Pattern": "^T654",
"Options": "i"
}
},
"status": {
"$eq": 1
}
}
},
{
"$project": {
"make": 1,
"model": 1,
"name": 1
}
}
]
The following operators are currently supported with more in development:
- Match
- Unwind
- Project
- Lookup
- Sort
- Limit
- Skip
Directories ¶
Path | Synopsis |
---|---|
The util package provides handy tools that are used by the gomongo package as well as by the implementation of the gomongo package.
|
The util package provides handy tools that are used by the gomongo package as well as by the implementation of the gomongo package. |