Features

Database Support

One generic repository interface over MongoDB, SQL and DynamoDB, with built-in CRUD, pagination and custom repository support.

Ginboot provides a powerful and flexible multi-database support system through a generic repository interface. This allows you to interact with different database systems (MongoDB, SQL, DynamoDB) using a consistent API, making your application more modular, testable, and adaptable to various data storage needs.

Generic Repository Interface

The core of Ginboot's database abstraction is the GenericRepository[T any] interface. This interface defines a comprehensive set of common data access operations, ensuring a uniform way to interact with different database types.

type GenericRepository[T any] interface {
	FindById(id string) (T, error)
	FindAllById(ids []string) ([]T, error)
	Save(doc T) error
	SaveOrUpdate(doc T) error
	SaveAll(docs []T) error
	Update(doc T) error
	Delete(id string) error
	FindOneBy(field string, value interface{}) (T, error)
	FindOneByFilters(filters map[string]interface{}) (T, error)
	FindBy(field string, value interface{}) ([]T, error)
	FindByFilters(filters map[string]interface{}) ([]T, error)
	FindAll(options ...interface{}) ([]T, error)
	FindAllPaginated(pageRequest PageRequest) (PageResponse[T], error)
	FindByPaginated(pageRequest PageRequest, filters map[string]interface{}) (PageResponse[T], error)
	CountBy(field string, value interface{}) (int64, error)
	CountByFilters(filters map[string]interface{}) (int64, error)
	ExistsBy(field string, value interface{}) (bool, error)
	ExistsByFilters(filters map[string]interface{}) (bool, error)
}

Document Interface

For SQL and DynamoDB repositories, your data models must implement the Document interface, which provides the table/collection name.

type Document interface {
	GetTableName() string
}

Pagination Structures

Ginboot provides standardized structures for handling pagination requests and responses.

type SortField struct {
	Field     string `json:"field"`
	Direction int    `json:"direction"` // 1 for ascending, -1 for descending
}

type PageRequest struct {
	Page int       `json:"page"`
	Size int       `json:"size"`
	Sort SortField `json:"sort"`
}

type PageResponse[T interface{}] struct {
	Contents         []T         `json:"content"`
	NumberOfElements int         `json:"numberOfElements"`
	Pageable         PageRequest `json:"pageable"`
	TotalPages       int         `json:"totalPages"`
	TotalElements    int         `json:"totalElements"`
}

Choosing a backend

The three supported backends share the interface above — pick the tab for the one you're using.

Ginboot offers robust support for MongoDB through MongoConfig for connection management and MongoRepository for data operations.

MongoDB Configuration

Use ginboot.NewMongoConfig() to build your MongoDB connection string. You can specify host, port, credentials, database name, and additional options.

import (
	"log"
	"github.com/klass-lk/ginboot"
)

func connectMongo() *mongo.Database {
	config := ginboot.NewMongoConfig().
		WithHost("localhost", 27017).
		WithDatabase("mydatabase").
		WithCredentials("myuser", "mypassword").
		WithOption("authSource", "admin")

	db, err := config.Connect()
	if err != nil {
		log.Fatalf("Failed to connect to MongoDB: %v", err)
	}
	fmt.Println("Connected to MongoDB!")
	return db
}

MongoDB Repository Example

Define your document struct with bson tags for MongoDB field mapping and a ginboot:"_id" tag for the primary key if it's not named ID.

import (
	"fmt"
	"go.mongodb.org/mongo-driver/mongo"
	"github.com/klass-lk/ginboot"
)

type User struct {
    ID   string `bson:"_id" ginboot:"_id"` // ginboot:_id helps the repository identify the ID field
    Name string `bson:"name"`
    Age  int    `bson:"age"`
}

// NewMongoRepository creates a new MongoDB repository instance.
// The collection name is typically the plural of your entity name.
func NewUserRepository(db *mongo.Database) *UserRepository {
    return &UserRepository{
        MongoRepository: ginboot.NewMongoRepository[User](db, "users"),
    }
}

// Example usage of the MongoDB repository
func main() {
	db := connectMongo() // Assume connectMongo() returns *mongo.Database
	repo := ginboot.NewMongoRepository[User](db, "users")

	// Save a new user
	user := User{ID: "1", Name: "John Doe", Age: 30}
	err := repo.Save(user)
	if err != nil { log.Fatal(err) }
	fmt.Println("User saved:", user.Name)

	// Find user by ID
	foundUser, err := repo.FindById("1")
	if err != nil { log.Fatal(err) }
	fmt.Println("Found user:", foundUser.Name)

	// Update user
	foundUser.Age = 31
	err = repo.Update(foundUser)
	if err != nil { log.Fatal(err) }
	fmt.Println("User updated:", foundUser.Name)

	// Find users by filter
	filters := map[string]interface{}{"age": 31}
	users, err := repo.FindByFilters(filters)
	if err != nil { log.Fatal(err) }
	fmt.Println("Users with age 31:", len(users))

	// Paginated query
	pageRequest := ginboot.PageRequest{Page: 1, Size: 10, Sort: ginboot.SortField{Field: "name", Direction: 1}}
	pageResponse, err := repo.FindAllPaginated(pageRequest)
	if err != nil { log.Fatal(err) }
	fmt.Println("Paginated results:", len(pageResponse.Contents))
}

Customizing Repositories

You can easily extend Ginboot's generic repositories to add database-specific methods or custom business logic. This is done by embedding the generic repository within your own custom repository struct.

import (
	"go.mongodb.org/mongo-driver/mongo"
	"github.com/klass-lk/ginboot"
)

type UserRepository struct {
    *ginboot.MongoRepository[User] // Embed the generic repository
}

func NewUserRepository(db *mongo.Database) *UserRepository {
    return &UserRepository{
        MongoRepository: ginboot.NewMongoRepository[User](db, "users"),
    }
}

// Add a custom method specific to UserRepository
func (r *UserRepository) FindUsersByStatus(status string) ([]User, error) {
    // You can use the embedded generic repository methods
    return r.FindBy("status", status)
}

// Or implement a completely custom query
func (r *UserRepository) GetActiveUsersCount() (int64, error) {
    // Access the underlying collection directly if needed
    // return r.collection.CountDocuments(context.Background(), bson.M{"status": "active"})
    return r.CountBy("status", "active")
}

On this page