# Understanding the Flow from HTTP Request to Database in Go Clean Architecture

> Explore the Go clean architecture request-to-database flow. See how router controller use-case and repository layers handle data atomically. Learn from manakuro/golang-clean-architecture.

- Repository: [manato/golang-clean-architecture](https://github.com/manakuro/golang-clean-architecture)
- Tags: deep-dive
- Published: 2026-03-06

---

**In the manakuro/golang-clean-architecture repository, every HTTP request traverses five strict layers—Router → Controller → Use-case → Repository Interface → Concrete Implementation—with database transactions managed atomically at the use-case level via the `DBRepository.Transaction` interface.**

This Go project demonstrates classic Clean Architecture principles by enforcing dependency inversion and separation of concerns. The request flow ensures that business logic remains decoupled from HTTP handlers and database implementations, making the codebase testable and framework-agnostic.

## The Five-Layer Request Flow

The architecture processes incoming requests through a unidirectional pipeline where each layer has a single, well-defined responsibility.

### 1. HTTP Routing with Echo

The entry point resides in [`pkg/infrastructure/router/router.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/infrastructure/router/router.go), where the Echo framework registers endpoints and maps them to controller methods. The `NewRouter` function wires HTTP verbs to specific handler functions, establishing the first layer of the application boundary.

```go
e := echo.New()
appCtrl := controller.NewAppController(...)
router.NewRouter(e, appCtrl) // Registers GET /users and POST /users

```

When a client sends a request, the router matches the URL pattern and forwards the context to the appropriate controller method.

### 2. Request Handling in Controllers

Controllers in [`pkg/adapter/controller/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/controller/user.go) act as the presentation layer, converting HTTP requests into domain objects and responses into JSON. The `userController` struct embeds the use-case interface, allowing it to invoke business operations without knowing implementation details.

Key methods include `GetUsers` for retrieval and `CreateUser` for persistence, both following the pattern of binding request data, invoking the use-case, and returning formatted responses.

### 3. Business Logic in Use-Cases

The use-case layer in [`pkg/usecase/usecase/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/usecase/usecase/user.go) contains the application-specific business rules (interactors). The `userUsecase` struct orchestrates data flow between controllers and repositories, ensuring transaction boundaries wrap multi-step operations.

For read operations, `List()` delegates directly to the repository. For writes, `Create()` manages atomic transactions through the `DBRepository` interface, ensuring database consistency even when multiple tables are involved.

### 4. Repository Interfaces

Before reaching the database, requests pass through abstract interfaces defined in `pkg/usecase/repository/`. The [`user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/user.go) file declares `UserRepository` with methods like `FindAll()` and `Create()`, while [`db.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/db.go) defines `DBRepository` with the critical `Transaction()` method.

These interfaces live in the **use-case layer**, not the infrastructure layer, ensuring that business logic depends on abstractions rather than concrete database technologies.

### 5. Database Implementation with Gorm

Concrete implementations reside in `pkg/adapter/repository/`. The [`user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/user.go) file provides Gorm-specific query logic using `ur.db.Find()` and `ur.db.Create()`, while [`db.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/db.go) implements transaction management with `tx := r.db.Begin()`, deferring commit or rollback based on function execution results.

This layer is the only component aware of Gorm, allowing the entire application to swap database drivers by changing just these adapter files.

## Transaction Management Strategy

The repository implements a robust transaction pattern that keeps business logic clean while ensuring atomicity. When `userUsecase.Create()` receives a request, it invokes `uu.dBRepository.Transaction()` with an anonymous function containing the actual database work.

```go
func (uu *userUsecase) Create(u *model.User) (*model.User, error) {
    data, err := uu.dBRepository.Transaction(func(i interface{}) (interface{}, error) {
        // Atomic database operation
        return uu.userRepository.Create(u)
    })
    // Type assertion returns *model.User after successful commit
    return data.(*model.User), err
}

```

The concrete implementation in [`pkg/adapter/repository/db.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/repository/db.go) handles the Gorm-specific transaction lifecycle: beginning the transaction, executing the supplied function, committing on success, or rolling back on error/panic. This pattern prevents leaked connections and ensures data integrity without cluttering business logic with database boilerplate.

## Practical Implementation Examples

### Registering Routes

The router establishes the HTTP interface in [`pkg/infrastructure/router/router.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/infrastructure/router/router.go):

```go
e.GET("/users", func(c echo.Context) error {
    return appCtrl.User.GetUsers(c)
})
e.POST("/users", func(c echo.Context) error {
    return appCtrl.User.CreateUser(c)
})

```

### Controller Request Binding

The controller in [`pkg/adapter/controller/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/controller/user.go) handles input validation and response formatting:

```go
func (uc *userController) CreateUser(ctx Context) error {
    var params model.User
    if err := ctx.Bind(&params); err != nil {
        return err
    }
    
    created, err := uc.userUsecase.Create(&params)
    if err != nil {
        return err
    }
    
    return ctx.JSON(http.StatusCreated, created)
}

```

### Concrete Gorm Repository

The database adapter in [`pkg/adapter/repository/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/repository/user.go) executes the actual SQL through Gorm:

```go
func (ur *userRepository) Create(u *model.User) (*model.User, error) {
    if err := ur.db.Create(u).Error; err != nil {
        return nil, err
    }
    return u, nil
}

```

## Summary

- **Dependency Inversion**: The use-case layer depends on `UserRepository` and `DBRepository` interfaces, not concrete Gorm implementations.
- **Layer Isolation**: HTTP handling (`controller`), business rules (`usecase`), and database access (`adapter/repository`) remain strictly separated.
- **Transaction Safety**: The `DBRepository.Transaction` method in [`pkg/adapter/repository/db.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/repository/db.go) encapsulates Gorm's transaction logic, exposing a clean functional interface to the use-case layer.
- **Framework Decoupling**: Only the `router` and `adapter/repository` packages import Echo and Gorm, respectively, making the core business logic framework-agnostic.

## Frequently Asked Questions

### How does the repository pattern prevent database leaks in this architecture?

The `DBRepository.Transaction` implementation in [`pkg/adapter/repository/db.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/repository/db.go) uses Go's `defer` statement to ensure `tx.Rollback()` or `tx.Commit()` always executes, even if the use-case function panics. This pattern guarantees that database connections return to the pool regardless of application errors.

### Can I replace Gorm with raw SQL or another ORM without changing the use-case code?

Yes. Since [`pkg/usecase/usecase/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/usecase/usecase/user.go) only references interfaces defined in `pkg/usecase/repository/`, you can rewrite [`pkg/adapter/repository/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/repository/user.go) and [`db.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/db.go) to use `database/sql`, SQLx, or another ORM. The use-case layer remains unchanged because it depends on the `UserRepository` and `DBRepository` abstractions, not Gorm-specific types.

### Where should I add input validation in this Clean Architecture flow?

Validation occurs in two stages: format-level validation (JSON binding, type checking) happens in the controller at [`pkg/adapter/controller/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/controller/user.go), while business-rule validation (domain invariants, uniqueness checks) belongs in the use-case layer at [`pkg/usecase/usecase/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/usecase/usecase/user.go). This separation ensures HTTP concerns don't leak into business logic and vice versa.

### How does the router know which controller method to invoke?

The `router.NewRouter` function in [`pkg/infrastructure/router/router.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/infrastructure/router/router.go) receives an `AppController` struct containing all controller instances. It explicitly maps URL patterns to specific methods (e.g., `e.GET("/users", ctrl.User.GetUsers)`), creating a declarative routing table that centralizes API endpoint definitions in one location.