How to Soft Delete with MongoDB and Go

How to Soft Delete with MongoDB and Go

If you have to implement an API with CRUD operations on MongoDB with Go, but with the caveat that the documents on a collection had to be deleted using the soft delete pattern, then continue reading to find out how.

You can find the full source code here: https://github.com/arielcr/soft-delete-mongodb-go

If you are using a SQL database this is a common and easy task, and probably easier if you do it on any other language with a framework . I searched about what was the best way to do this in MongoDB, and I found several implementation examples, but none of them were very clear or straightforward, and there were just a few examples in Go.

So, after looking at the official MongoDB driver for Go and the examples on the MongoDB documentation, it all ends up on having a well-defined struct for your document with the right json and bson attributes.

So, let's say you have a User document, with the following struct:

type User struct {
    ID        primitive.ObjectID `json:"id" bson:"_id"`
    Name      string             `json:"name" bson:"name"`
    Email     string             `json:"email" bson:"email"`
}

If you want it to support the soft delete pattern, you have to include the deleted_at field:

type User struct {
    ID        primitive.ObjectID `json:"id" bson:"_id"`
    Name      string             `json:"name" bson:"name"`
    Email     string             `json:"email" bson:"email"`
    DeletedAt time.Time          `json:"-" bson:"deleted_at,omitempty"`
}

Notice how we are excluding this field from the json response with the - attribute because we don't want this to be returned as part of the response data, also take a look at the omitempty bson attribute, so this does not get into the document if it is not specified.

First, we insert our User (we don't have to include the deleted_at field):

user := entities.User{
        Name:  "John Doe",
        Email: "john@doe.com",
    }

_, err := r.collection().InsertOne(ctx, user)

We want to get only the documents that do not have the deleted_at field in them. We do this by defining the right filter when we get the data:

filter := bson.M{
        "_id": userID,
        "deleted_at": bson.M{
            "$exists": false,
        },
    }

err := r.collection().FindOne(ctx, filter).Decode(&user)

The key here is to add the "$exists": false condition to the filter, so we only get the records where this field does not exist.

So, to delete a document we just create the deleted_at field:

filter := bson.M{
        "_id": userID,
        "deleted_at": bson.M{
            "$exists": false,
        },
    }
    updater := bson.D{
        primitive.E{
            Key: "$set",
            Value: bson.D{
                primitive.E{
                    Key:   "deleted_at",
                    Value: time.Now(),
                },
            },
        },
    }

result, err := r.collection().UpdateOne(ctx, filter, updater)

So, that's it, not too hard, but it was kind of difficult to find a suitable approach for Go.

Hope you find this useful!

There's a fully functional example for this here: https://github.com/arielcr/soft-delete-mongodb-go