Backend Structure
Overview
The backend is structured to follow the principles of separation of concerns. Each layer is responsible for a specific task, making the code more modular and easier to maintain.
Router
The router is set up using the gin-gonic framework. It includes routes for public and protected endpoints. Middleware (such as authentication) is applied at this level.
Example:
package routes
import (
"controllers"
"github.com/gin-gonic/gin"
)
func SetupRouter() *gin.Engine {
router := gin.Default()
// Public routes
public := router.Group("/api")
{
public.POST("/login", controllers.Login)
public.POST("/register", controllers.Register)
}
// Protected routes (requires authentication)
protected := router.Group("/api")
protected.Use(AuthMiddleware())
{
protected.GET("/profile", controllers.GetProfile)
protected.POST("/update-profile", controllers.UpdateProfile)
}
return router
}
Route Groups
The gin-gonic framework allows organizing routes into groups. This improves readability and makes it easier to apply middleware to specific groups of routes.
Public Routes: Do not require authentication. These routes are accessible to all users.
Protected Routes: Require the user to be authenticated. Middleware is used to enforce this requirement.
Middleware in the Router
Middleware can be applied to route groups or individual routes. For example:
protected.Use(AuthMiddleware())
This ensures that all routes under the protected group require a valid token for access.
Router Initialization
The router is initialized and run in the main.go file:
package main
import (
"routes"
)
func main() {
router := routes.SetupRouter()
router.Run(":8080") // Start the server on port 8080
}
Controllers
Controllers handle incoming HTTP requests and return appropriate responses. They typically:
Parse request data (e.g., JSON payloads).
Call services or business logic layers to perform operations.
Return HTTP responses to the client.
Example:
func Login(c *gin.Context) {
email := c.PostForm("email")
password := c.PostForm("password")
token, err := authService.Login(email, password)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
return
}
c.JSON(http.StatusOK, gin.H{"token": token})
}
Middleware
Middleware is used to handle cross-cutting concerns like authentication, logging, or error handling. Middleware functions are executed before or after controllers handle requests.
Example Authentication Middleware:
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" || !authService.IsValidToken(token) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
c.Next()
}
}
Models
Models represent the structure of data stored in the MariaDB database. They define the schema for each table and map the database structure to Go objects. Models are managed using the gorm library.
Example User Model:
package models
import "gorm.io/gorm"
type User struct {
ID uint `gorm:"primaryKey"`
Email string `gorm:"unique;not null"`
Password string `gorm:"not null"`
Token string
}
func Migrate(db *gorm.DB) {
db.AutoMigrate(&User{})
}
Explanation:
ID: The primary key for the users table.
Email: A unique and non-nullable field for storing user emails.
Password: A non-nullable field for storing hashed passwords.
Token: Used for authentication and session management.
To migrate models to the database, run the migration function during initialization:
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"models"
)
func InitDB() *gorm.DB {
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("Failed to connect to the database!")
}
models.Migrate(db)
return db
}