Modular project structure with Gin in Go

Gin is a fast and minimal HTTP web framework for Go. When working on larger projects, it is essential to structure the code in a modular way to improve maintainability, testability, and scalability.

1. Project Structure

A modular structure can be organized as follows:

myapp/
├── cmd/
│   └── main.go
├── internal/
│   ├── server/
│   │   └── server.go
│   ├── handler/
│   │   ├── user.go
│   │   └── product.go
│   ├── service/
│   │   ├── user.go
│   │   └── product.go
│   ├── repository/
│   │   ├── user.go
│   │   └── product.go
│   └── model/
│       ├── user.go
│       └── product.go
└── go.mod

2. Entry Point

In the main.go file, we initialize the server:

package main

import "myapp/internal/server"

func main() {
    server.Start()
}

3. Starting the Server

In the server package we configure the router:

package server

import (
    "github.com/gin-gonic/gin"
    "myapp/internal/handler"
)

func Start() {
    r := gin.Default()

    // Register routes
    handler.RegisterUserRoutes(r)
    handler.RegisterProductRoutes(r)

    r.Run() // default :8080
}

4. Route Handling

In the handler package we separate routes by domain:

package handler

import (
    "github.com/gin-gonic/gin"
    "myapp/internal/service"
)

func RegisterUserRoutes(r *gin.Engine) {
    group := r.Group("/users")
    group.GET("/", getAllUsers)
    group.GET("/:id", getUserByID)
}

func getAllUsers(c *gin.Context) {
    users := service.GetAllUsers()
    c.JSON(200, users)
}

func getUserByID(c *gin.Context) {
    id := c.Param("id")
    user, err := service.GetUserByID(id)
    if err != nil {
        c.JSON(404, gin.H{"error": "User not found"})
        return
    }
    c.JSON(200, user)
}

5. Services and Repositories

Services encapsulate business logic, while repositories handle data access.

package service

import (
    "myapp/internal/model"
    "myapp/internal/repository"
)

func GetAllUsers() []model.User {
    return repository.FindAllUsers()
}

func GetUserByID(id string) (model.User, error) {
    return repository.FindUserByID(id)
}
package repository

import (
    "fmt"
    "myapp/internal/model"
)

var users = []model.User{
    {ID: "1", Name: "Alice"},
    {ID: "2", Name: "Bob"},
}

func FindAllUsers() []model.User {
    return users
}

func FindUserByID(id string) (model.User, error) {
    for _, u := range users {
        if u.ID == id {
            return u, nil
        }
    }
    return model.User{}, fmt.Errorf("user not found")
}

6. Models

Finally, models describe the domain entities:

package model

type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

Conclusion

Structuring a Gin application in a modular way promotes code clarity and separation of concerns. This approach is particularly useful in medium and large projects, making testing, extensions, and refactoring easier.

Back to top