Tuesday, 14 January 2025

Folder structure often used with Gin

Below is an example of a folder structure often used with Gin in large-scale projects. Gin itself does not enforce a strict layout, because it follows a minimalistic philosophy. However, the community usually follows certain best practices to keep the code organized.

my-gin-app/
├── cmd/
│   └── server/
│       └── main.go
├── config/
│   └── config.go
├── controllers/    (or handlers/)
│   └── user_controller.go
├── middlewares/
│   └── auth.go
├── models/
│   └── user.go
├── routes/
│   └── router.go
├── services/       (business logic)
│   └── user_service.go
├── repository/     (DB access, optional)
│   └── user_repository.go
├── go.mod
└── go.sum

Directory Overview (Suggested)

  1. cmd/server/main.go

    • The application’s entry point.
    • Initializes the Gin engine, loads configs, sets up routes, etc.
    • Placing main.go inside cmd/server/ is a common Go convention to keep the root clean.
  2. config/

    • Handles configuration (e.g., reading .env, using Viper, etc.).
    • For instance, config.go might have func InitConfig() to load environment variables or database connection info.
  3. controllers/ (or handlers/)

    • Houses functions that handle requests (CRUD, login, etc.).
    • Example: user_controller.go has func GetUsers(c *gin.Context) and func CreateUser(c *gin.Context).
  4. middlewares/

    • Contains middleware (auth, logging, recovery, etc.).
    • Example: auth.go with func AuthMiddleware() gin.HandlerFunc.
  5. models/

    • Contains structs mapping to database tables (e.g., User with ID, Name, Email...).
    • If using GORM, add the appropriate tags (gorm:"...").
  6. routes/

    • Contains a SetupRouter() function to group and register routes, apply middleware, etc.

    • Example:

      package routes
      
      import (
        "github.com/gin-gonic/gin"
        "my-gin-app/controllers"
        "my-gin-app/middlewares"
      )
      
      func SetupRouter() *gin.Engine {
        r := gin.Default()
      
        r.Use(middlewares.LoggerMiddleware())
      
        user := r.Group("/users")
        {
          user.GET("/", controllers.GetUsers)
          user.POST("/", controllers.CreateUser)
        }
        return r
      }
      
  7. services/

    • Contains business logic (e.g., user_service.go for email sending, domain rules, etc.).
    • Your controllers can remain thin, delegating heavier tasks to these service functions.
  8. repository/ (optional)

    • Separates all database interactions (queries) from services, promoting cleaner architecture.
    • In simpler projects, you can skip this folder and keep queries in services.
  9. go.mod / go.sum

    • Standard Go files for dependency management.
  10. (Additional directories)

    • migrations/ (if using Goose or GORM migrations).
    • test/ (unit or integration tests).
    • docs/ (API documentation, Swagger, etc.).
    • scripts/ (CI/CD scripts).

Mini Example

cmd/server/main.go

package main

import (
    "log"

    "my-gin-app/config"
    "my-gin-app/routes"
)

func main() {
    // 1. Initialize config, DB, etc.
    config.InitConfig()

    // 2. Set up Gin router
    r := routes.SetupRouter()

    // 3. Run
    if err := r.Run(":8080"); err != nil {
        log.Fatal(err)
    }
}

config/config.go

package config

import (
    "fmt"
    "os"
    // import "github.com/joho/godotenv" if you need to load .env
    // import GORM libraries if you plan to connect to a DB
)

func InitConfig() {
    // For example: load environment variables
    // godotenv.Load()

    fmt.Println("Config loaded, database connected... (placeholder)")
}

models/user.go

package models

import "time"

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Name      string    `gorm:"size:255"`
    Email     string    `gorm:"size:255;unique"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

controllers/user_controller.go

package controllers

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "my-gin-app/services"
)

func GetUsers(c *gin.Context) {
    users, err := services.GetAllUsers()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, users)
}

func CreateUser(c *gin.Context) {
    var input struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := services.CreateUser(input.Name, input.Email)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, user)
}

services/user_service.go

package services

import (
    "fmt"
    "my-gin-app/models"
)

// Mocked data for illustration
func GetAllUsers() ([]models.User, error) {
    return []models.User{
        {ID: 1, Name: "Alice", Email: "[email protected]"},
        {ID: 2, Name: "Bob", Email: "[email protected]"},
    }, nil
}

func CreateUser(name, email string) (models.User, error) {
    if email == "" {
        return models.User{}, fmt.Errorf("email is required")
    }
    // Suppose we insert into DB and return the created record
    user := models.User{
        ID:    3,
        Name:  name,
        Email: email,
    }
    return user, nil
}

routes/router.go

package routes

import (
    "github.com/gin-gonic/gin"
    "my-gin-app/controllers"
)

func SetupRouter() *gin.Engine {
    r := gin.Default()

    userGroup := r.Group("/users")
    {
        userGroup.GET("/", controllers.GetUsers)
        userGroup.POST("/", controllers.CreateUser)
    }

    return r
}

Conclusion

  • Gin does not provide a default or strict folder structure. Instead, it trusts developers to organize the project.
  • For mid- to large-sized projects, many people separate controllers, services, models, routes, middlewares, etc., to keep the codebase maintainable.
  • The example above is a commonly used pattern in the Go community:
    1. cmd/server/: main entry point
    2. config/: app configurations (env, DB)
    3. controllers/ (or handlers/): handling HTTP requests
    4. services/: business/domain logic
    5. models/: database schemas (e.g., with GORM)
    6. routes/: routing setup
    7. middlewares/: shared logic for requests (JWT, logging, etc.)
    8. repository/: optional layer for DB queries
    9. go.mod/go.sum: module dependencies

Feel free to modify this structure according to your team’s needs and project size.

No comments:

Post a Comment

Golang Advanced Interview Q&A