Repositories

Learn how to access the Bun ORM using repositories in Caesar.

Overview

Repositories are the recommended pattern for accessing the Bun ORM in Caesar. They provide a clean, organized way to manage your database operations with basic CRUD functionalities. By convention, repositories are located in the ./app/repositories folder.

Defining a Repository

Here's how you define a repository in Caesar:

package repositories

import (
	"myapp/app/models"

	"github.com/caesar-rocks/orm"
)

type UsersRepository struct {
	*orm.Repository[models.User]
}

func NewUsersRepository(db *orm.Database) *UsersRepository {
	return &UsersRepository{Repository: &orm.Repository[models.User]{Database: db}}
}

In the example above, the UsersRepository is defined to handle CRUD operations for the User model.

CRUD Queries

The repository pattern in Caesar provides the following CRUD queries:

  • Create: Inserts a new record into the database.
  • FindAll: Retrieves all records from the database.
  • FindOneBy: Finds a single record based on a specified field and value.
  • UpdateOneWhere: Updates a record based on a specified field and value.
  • DeleteOneWhere: Deletes a record based on a specified field and value.

Custom Queries

While the repository provides basic CRUD operations, you can easily override or add your own custom queries in the repository file. This gives you the flexibility to tailor database interactions to your application's specific needs.

Example of a Custom Query:

func (r *UsersRepository) FindActiveUsers(ctx context.Context) ([]models.User, error) {
	var users []models.User
	err := r.NewSelect().
		Model(&users).
		Where("status = ?", "active").
		Scan(ctx)
	if err != nil {
		return nil, err
	}
	return users, nil
}

In the custom query example above, the FindActiveUsers method is defined to find all users with an active status.

Usage Example with Controllers

Here's an example of how you might use a repository in your application to manage records within a controller:

User Controller

Define a controller that uses the UsersRepository:

package controllers

import (
	"context"
	"log"
	"net/http"

	"myapp/app/models"
	"myapp/app/repositories"

	caesar "github.com/caesar-rocks/core"
)

type UsersController struct {
	repo *repositories.UsersRepository
}

func NewUsersController(repo *repositories.UsersRepository) *UsersController {
	return &UsersController{repo: repo}
}

func (ctr *UsersController) CreateUser(ctx *caesar.CaesarCtx) error {
	var data struct {
		Email    string `json:"email"`
		FullName string `json:"full_name"`
		Password string `json:"password"`
	}

	if err := ctx.DecodeJSON(&data); err != nil {
		return caesar.NewError(http.StatusBadRequest, "Invalid request payload")
	}

	hashedPassword := HashPassword(data.Password) // Assume this function is defined elsewhere
	user := &models.User{
		Email:    data.Email,
		FullName: data.FullName,
		Password: hashedPassword,
	}

	if err := ctr.repo.Create(ctx.Context(), user); err != nil {
		return caesar.NewError(http.StatusInternalServerError, "Failed to create user")
	}

	return ctx.SendJSON(user, http.StatusCreated)
}

func (ctr *UsersController) GetAllUsers(ctx *caesar.CaesarCtx) error {
	users, err := ctr.repo.FindAll(ctx.Context())
	if err != nil {
		return caesar.NewError(http.StatusInternalServerError, "Failed to fetch users")
	}

	return ctx.SendJSON(users)
}

func (ctr *UsersController) GetActiveUsers(ctx *caesar.CaesarCtx) error {
	users, err := ctr.repo.FindActiveUsers(ctx.Context())
	if err != nil {
		return caesar.NewError(http.StatusInternalServerError, "Failed to fetch active users")
	}

	return ctx.SendJSON(users)
}

Registering Routes

Register the controller methods in your application's route configuration:

package config

import (
	"myapp/app/controllers"
	"myapp/app/repositories"
	caesar "github.com/caesar-rocks/core"
)

func RegisterRoutes(usersRepo *repositories.UsersRepository) *caesar.Router {
	usersController := controllers.NewUsersController(usersRepo)

	router := caesar.NewRouter()

	router.Post("/users", usersController.CreateUser)
	router.Get("/users", usersController.GetAllUsers)
	router.Get("/users/active", usersController.GetActiveUsers)

	return router
}

Registering Repositories as Providers

To be able to use the repository in controllers or other parts of your application, you need to register it as a provider in the config/app.go file:

package config

import (
	"myapp/app/repositories"

	"github.com/caesar-rocks/core"
)

func ProvideApp() *core.App {
	app := core.NewApp(&core.AppConfig{
		Addr: ":8080",
	})

	// Register repositories
	app.RegisterProviders(
		repositories.NewUsersRepository,
	)

	// Other providers and invokers
	app.RegisterProviders(
		// other providers ...
	)

	app.RegisterInvokers(
		// invokers ...
	)

	return app
}

In this example, the NewUsersRepository is registered as a provider, which makes it available for dependency injection in other parts of the application, such as controllers.

Summary

Repositories in Caesar provide a structured way to interact with your database using the Bun ORM. They encapsulate basic CRUD operations and allow you to add or override custom queries to fit your specific requirements. By following the convention of placing repositories in the ./app/repositories folder, you can easily manage and locate your database access logic.

By leveraging controllers, you can cleanly separate your application's business logic and database interactions, ensuring a maintainable and scalable codebase. Don't forget to register your repositories as providers in the config/app.go file to make them available throughout your application.