Skip to content

CRUD Operations

goodm provides full-lifecycle CRUD functions that handle ID generation, timestamps, hooks, validation, and immutable field enforcement. It also provides raw passthrough operations for performance-critical paths.

Create

func Create(ctx context.Context, model interface{}, opts ...CreateOptions) error

Inserts a new document with the full ODM lifecycle:

  1. Generates ID (if zero)
  2. Sets CreatedAt (if zero) and UpdatedAt
  3. Applies schema defaults to zero-valued fields
  4. Sets Version to 0
  5. Runs BeforeCreate hook
  6. Validates against schema (required, enum, min/max)
  7. Inserts into MongoDB
  8. Runs AfterCreate hook
user := &User{Email: "alice@example.com", Name: "Alice", Age: 30}
err := goodm.Create(ctx, user)
// user.ID, user.CreatedAt, user.UpdatedAt are now set

FindOne

func FindOne(ctx context.Context, filter interface{}, result interface{}, opts ...FindOptions) error

Finds a single document matching the filter. Returns ErrNotFound if no document matches.

user := &User{}
err := goodm.FindOne(ctx, bson.D{{Key: "email", Value: "alice@example.com"}}, user)
if errors.Is(err, goodm.ErrNotFound) {
    // not found
}

Find

func Find(ctx context.Context, filter interface{}, results interface{}, opts ...FindOptions) error

Finds all matching documents. results must be a pointer to a slice.

var users []User
err := goodm.Find(ctx, bson.D{{Key: "role", Value: "admin"}}, &users)

With options:

var users []User
err := goodm.Find(ctx, bson.D{}, &users, goodm.FindOptions{
    Limit: 20,
    Skip:  40,
    Sort:  bson.D{{Key: "created_at", Value: -1}},
})

FindCursor

func FindCursor(ctx context.Context, filter interface{}, model interface{}, opts ...FindOptions) (*mongo.Cursor, error)

Returns a raw *mongo.Cursor for streaming large result sets. The model parameter is used only for collection lookup.

cursor, err := goodm.FindCursor(ctx, bson.D{}, &User{}, goodm.FindOptions{
    Sort: bson.D{{Key: "created_at", Value: 1}},
})
if err != nil {
    log.Fatal(err)
}
defer cursor.Close(ctx)

for cursor.Next(ctx) {
    var user User
    cursor.Decode(&user)
    // process user
}

Update

func Update(ctx context.Context, model interface{}, opts ...UpdateOptions) error

Replaces an existing document with the full ODM lifecycle:

  1. Requires non-zero ID
  2. Fetches existing document from DB (only if immutable fields exist)
  3. Validates immutable fields (compares with existing doc)
  4. Runs BeforeSave hook
  5. Validates against schema
  6. Increments Version and sets UpdatedAt
  7. Replaces document in MongoDB with version check (__v filter)
  8. Returns ErrVersionConflict if another process modified the document
  9. Runs AfterSave hook
user.Age = 31
err := goodm.Update(ctx, user)

If an immutable field has changed, Update returns a ValidationErrors with the offending field.

Delete

func Delete(ctx context.Context, model interface{}, opts ...DeleteOptions) error

Removes a document by its ID with hooks:

  1. Requires non-zero ID
  2. Runs BeforeDelete hook
  3. Deletes from MongoDB
  4. Runs AfterDelete hook
err := goodm.Delete(ctx, user)

Raw Operations

These bypass hooks, validation, and immutable enforcement. Use them when you need direct MongoDB access for performance.

UpdateOne

func UpdateOne(ctx context.Context, filter, update, model interface{}, opts ...UpdateOptions) error

Partial update using MongoDB update operators. The model parameter is for collection lookup only.

err := goodm.UpdateOne(ctx,
    bson.D{{Key: "email", Value: "alice@example.com"}},
    bson.D{{Key: "$set", Value: bson.D{{Key: "age", Value: 31}}}},
    &User{},
)

Performance: Bypasses hooks, validation, and immutable field enforcement. You are responsible for data integrity.

DeleteOne

func DeleteOne(ctx context.Context, filter, model interface{}, opts ...DeleteOptions) error

Deletes by filter without requiring a loaded model.

err := goodm.DeleteOne(ctx,
    bson.D{{Key: "email", Value: "alice@example.com"}},
    &User{},
)

Performance: Bypasses hooks entirely.

Options

All operations accept an optional DB field to override the global database:

goodm.Create(ctx, user, goodm.CreateOptions{DB: otherDB})
goodm.FindOne(ctx, filter, result, goodm.FindOptions{DB: otherDB})
goodm.Update(ctx, user, goodm.UpdateOptions{DB: otherDB})
goodm.Delete(ctx, user, goodm.DeleteOptions{DB: otherDB})

Error Types

Error When
ErrNotFound FindOne/Update/Delete finds no matching document
ErrNoDatabase No database connection (Connect not called)
ErrVersionConflict Update detects another process modified the document (optimistic concurrency)
ValidationErrors Validation or immutable check failure