Skip to content

Database CRUD Example

This example demonstrates how to implement Create, Read, Update, Delete operations with GOE's database module.

Setup

go
package main

import (
    "go.oease.dev/goe/v2"
    "go.oease.dev/goe/v2/contract"
)

func main() {
    goe.New(goe.Options{
        WithHTTP: true,
        WithDB:   true,
        Providers: []any{
            NewUserRepository,
            NewUserService,
            NewUserHandler,
        },
        Invokers: []any{
            AutoMigrate,
            RegisterRoutes,
        },
    })
    
    goe.Run()
}

User Model

go
package model

import (
    "time"
    "gorm.io/gorm"
)

type User struct {
    ID        uint           `gorm:"primaryKey" json:"id"`
    Name      string         `gorm:"size:100;not null" json:"name"`
    Email     string         `gorm:"size:100;uniqueIndex;not null" json:"email"`
    Age       int            `gorm:"not null" json:"age"`
    Active    bool           `gorm:"default:true" json:"active"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

Repository Layer

go
package repository

import (
    "go.oease.dev/goe/v2/contract"
    "your-app/model"
)

type UserRepository struct {
    db contract.DB
}

func NewUserRepository(db contract.DB) *UserRepository {
    return &UserRepository{db: db}
}

func (r *UserRepository) Create(user *model.User) error {
    return r.db.Instance().Create(user).Error
}

func (r *UserRepository) GetByID(id uint) (*model.User, error) {
    var user model.User
    err := r.db.Instance().First(&user, id).Error
    if err != nil {
        return nil, err
    }
    return &user, nil
}

func (r *UserRepository) GetAll(limit, offset int) ([]model.User, error) {
    var users []model.User
    err := r.db.Instance().
        Limit(limit).
        Offset(offset).
        Order("created_at DESC").
        Find(&users).Error
    return users, err
}

func (r *UserRepository) Update(user *model.User) error {
    return r.db.Instance().Save(user).Error
}

func (r *UserRepository) Delete(id uint) error {
    return r.db.Instance().Delete(&model.User{}, id).Error
}

func (r *UserRepository) GetByEmail(email string) (*model.User, error) {
    var user model.User
    err := r.db.Instance().Where("email = ?", email).First(&user).Error
    if err != nil {
        return nil, err
    }
    return &user, nil
}

Service Layer

go
package service

import (
    "errors"
    "go.oease.dev/goe/v2/contract"
    "your-app/model"
    "your-app/repository"
)

type UserService struct {
    repo   *repository.UserRepository
    logger contract.Logger
}

func NewUserService(repo *repository.UserRepository, logger contract.Logger) *UserService {
    return &UserService{
        repo:   repo,
        logger: logger,
    }
}

func (s *UserService) CreateUser(name, email string, age int) (*model.User, error) {
    s.logger.Info("Creating user", "name", name, "email", email)
    
    // Check if user already exists
    existingUser, err := s.repo.GetByEmail(email)
    if err == nil && existingUser != nil {
        return nil, errors.New("user with this email already exists")
    }
    
    user := &model.User{
        Name:   name,
        Email:  email,
        Age:    age,
        Active: true,
    }
    
    if err := s.repo.Create(user); err != nil {
        s.logger.Error("Failed to create user", "error", err)
        return nil, err
    }
    
    s.logger.Info("User created successfully", "user_id", user.ID)
    return user, nil
}

func (s *UserService) GetUser(id uint) (*model.User, error) {
    s.logger.Info("Getting user", "user_id", id)
    
    user, err := s.repo.GetByID(id)
    if err != nil {
        s.logger.Error("Failed to get user", "user_id", id, "error", err)
        return nil, err
    }
    
    return user, nil
}

func (s *UserService) GetUsers(page, limit int) ([]model.User, error) {
    if page < 1 {
        page = 1
    }
    if limit < 1 || limit > 100 {
        limit = 10
    }
    
    offset := (page - 1) * limit
    
    s.logger.Info("Getting users", "page", page, "limit", limit)
    
    users, err := s.repo.GetAll(limit, offset)
    if err != nil {
        s.logger.Error("Failed to get users", "error", err)
        return nil, err
    }
    
    return users, nil
}

func (s *UserService) UpdateUser(id uint, name, email string, age int) (*model.User, error) {
    s.logger.Info("Updating user", "user_id", id)
    
    user, err := s.repo.GetByID(id)
    if err != nil {
        s.logger.Error("User not found", "user_id", id, "error", err)
        return nil, err
    }
    
    user.Name = name
    user.Email = email
    user.Age = age
    
    if err := s.repo.Update(user); err != nil {
        s.logger.Error("Failed to update user", "user_id", id, "error", err)
        return nil, err
    }
    
    s.logger.Info("User updated successfully", "user_id", id)
    return user, nil
}

func (s *UserService) DeleteUser(id uint) error {
    s.logger.Info("Deleting user", "user_id", id)
    
    // Check if user exists
    _, err := s.repo.GetByID(id)
    if err != nil {
        s.logger.Error("User not found", "user_id", id, "error", err)
        return err
    }
    
    if err := s.repo.Delete(id); err != nil {
        s.logger.Error("Failed to delete user", "user_id", id, "error", err)
        return err
    }
    
    s.logger.Info("User deleted successfully", "user_id", id)
    return nil
}

Handler Layer

go
package handler

import (
    "strconv"
    "github.com/gofiber/fiber/v3"
    "go.oease.dev/goe/v2/contract"
    "your-app/service"
)

type UserHandler struct {
    service *service.UserService
    logger  contract.Logger
}

func NewUserHandler(service *service.UserService, logger contract.Logger) *UserHandler {
    return &UserHandler{
        service: service,
        logger:  logger,
    }
}

type CreateUserRequest struct {
    Name  string `json:"name" validate:"required,min=2,max=100"`
    Email string `json:"email" validate:"required,email"`
    Age   int    `json:"age" validate:"required,min=1,max=120"`
}

type UpdateUserRequest struct {
    Name  string `json:"name" validate:"required,min=2,max=100"`
    Email string `json:"email" validate:"required,email"`
    Age   int    `json:"age" validate:"required,min=1,max=120"`
}

func (h *UserHandler) CreateUser(c fiber.Ctx) error {
    var req CreateUserRequest
    
    if err := c.BodyParser(&req); err != nil {
        h.logger.Error("Invalid request body", "error", err)
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid request body",
        })
    }
    
    // Validate request
    if err := validate.Struct(&req); err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Validation failed",
            "details": err.Error(),
        })
    }
    
    user, err := h.service.CreateUser(req.Name, req.Email, req.Age)
    if err != nil {
        return c.Status(500).JSON(fiber.Map{
            "error": err.Error(),
        })
    }
    
    return c.Status(201).JSON(user)
}

func (h *UserHandler) GetUser(c fiber.Ctx) error {
    id, err := strconv.ParseUint(c.Params("id"), 10, 32)
    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid user ID",
        })
    }
    
    user, err := h.service.GetUser(uint(id))
    if err != nil {
        return c.Status(404).JSON(fiber.Map{
            "error": "User not found",
        })
    }
    
    return c.JSON(user)
}

func (h *UserHandler) GetUsers(c fiber.Ctx) error {
    page, _ := strconv.Atoi(c.Query("page", "1"))
    limit, _ := strconv.Atoi(c.Query("limit", "10"))
    
    users, err := h.service.GetUsers(page, limit)
    if err != nil {
        return c.Status(500).JSON(fiber.Map{
            "error": "Failed to get users",
        })
    }
    
    return c.JSON(fiber.Map{
        "users": users,
        "page":  page,
        "limit": limit,
    })
}

func (h *UserHandler) UpdateUser(c fiber.Ctx) error {
    id, err := strconv.ParseUint(c.Params("id"), 10, 32)
    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid user ID",
        })
    }
    
    var req UpdateUserRequest
    if err := c.BodyParser(&req); err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid request body",
        })
    }
    
    if err := validate.Struct(&req); err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Validation failed",
            "details": err.Error(),
        })
    }
    
    user, err := h.service.UpdateUser(uint(id), req.Name, req.Email, req.Age)
    if err != nil {
        return c.Status(500).JSON(fiber.Map{
            "error": err.Error(),
        })
    }
    
    return c.JSON(user)
}

func (h *UserHandler) DeleteUser(c fiber.Ctx) error {
    id, err := strconv.ParseUint(c.Params("id"), 10, 32)
    if err != nil {
        return c.Status(400).JSON(fiber.Map{
            "error": "Invalid user ID",
        })
    }
    
    if err := h.service.DeleteUser(uint(id)); err != nil {
        return c.Status(500).JSON(fiber.Map{
            "error": err.Error(),
        })
    }
    
    return c.Status(204).Send(nil)
}

Routes Registration

go
package main

import (
    "github.com/gofiber/fiber/v3"
    "go.oease.dev/goe/v2/contract"
    "your-app/handler"
    "your-app/model"
)

func AutoMigrate(db contract.DB) {
    db.Instance().AutoMigrate(&model.User{})
}

func RegisterRoutes(httpKernel contract.HTTPKernel, userHandler *handler.UserHandler) {
    app := httpKernel.App()
    
    // API routes
    api := app.Group("/api")
    
    // User routes
    users := api.Group("/users")
    users.Post("/", userHandler.CreateUser)
    users.Get("/", userHandler.GetUsers)
    users.Get("/:id", userHandler.GetUser)
    users.Put("/:id", userHandler.UpdateUser)
    users.Delete("/:id", userHandler.DeleteUser)
    
    // Health check
    app.Get("/health", func(c fiber.Ctx) error {
        return c.JSON(fiber.Map{
            "status": "healthy",
            "service": "user-api",
        })
    })
}

Testing the API

bash
# Create a user
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"John Doe","email":"john@example.com","age":30}'

# Get all users
curl http://localhost:8080/api/users

# Get user by ID
curl http://localhost:8080/api/users/1

# Update user
curl -X PUT http://localhost:8080/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"John Smith","email":"john.smith@example.com","age":31}'

# Delete user
curl -X DELETE http://localhost:8080/api/users/1

Complete Example

Run the complete example:

bash
# Create project
mkdir goe-crud-example
cd goe-crud-example
go mod init goe-crud-example

# Install dependencies
go get go.oease.dev/goe/v2
go get github.com/go-playground/validator/v10

# Create .env file
echo "DB_DRIVER=sqlite
DB_PATH=./database.db
HTTP_PORT=8080
LOG_LEVEL=info" > .env

# Run the application
go run main.go

This example demonstrates:

  • Complete CRUD operations
  • Repository pattern for data access
  • Service layer for business logic
  • HTTP handlers for API endpoints
  • Proper error handling and logging
  • Input validation
  • Database migration

Released under the MIT License.