My App
Core Concepts

Routing

Routing

Ginboot provides a flexible and intuitive routing system built on top of Gin, enhancing it with controller-based organization, flexible handler signatures, and integrated context utilities.

Core Concepts

Controller Interface

Controllers in Ginboot are responsible for grouping related routes and their handlers. Any struct intended to be a controller must implement the Controller interface, which requires a Register method:

type Controller interface {
    Register(group *ControllerGroup)
}

The Register method is where you define all the routes and their associated handlers for that controller.

ControllerGroup

ControllerGroup is a wrapper around Gin's *gin.RouterGroup. It provides methods for registering routes (GET, POST, etc.) and creating nested sub-groups, while also integrating with Ginboot's custom Context and FileService.

Registering Controllers and Routes

Server-Level Registration

You register controllers with the main Server instance using RegisterController. This method automatically creates a ControllerGroup for your controller's base path and calls its Register method.

package main

import (
	"log"
	"github.com/klass-lk/ginboot"
	"your-project/internal/controller"
	"your-project/internal/service"
)

// Example UserController
type UserController struct {
	service *service.UserService
}

func NewUserController(s *service.UserService) *UserController {
	return &UserController{service: s}
}

func (c *UserController) ListUsers(ctx *ginboot.Context) ([]string, error) {
	// ... logic to list users ...
	return []string{"user1", "user2"}, nil
}

func (c *UserController) GetUser(ctx *ginboot.Context) (string, error) {
	userID := ctx.Param("id")
	// ... logic to get user by ID ...
	return fmt.Sprintf("User: %s", userID), nil
}

func (c *UserController) Register(group *ginboot.ControllerGroup) {
	group.GET("", c.ListUsers)       // GET /users
	group.GET("/:id", c.GetUser)     // GET /users/:id
}

func main() {
	server := ginboot.New()
	server.SetBasePath("/api/v1")

	userService := service.NewUserService() // Assume this exists
	userController := NewUserController(userService)

	// Register the UserController with a base path of "/users"
	server.RegisterController("/users", userController) // Routes will be /api/v1/users, /api/v1/users/:id

	log.Fatal(server.Start(8080))
}

Base Path Configuration

You can set a global base path for all routes registered with the server using server.SetBasePath(). This path will prefix all controller and group paths.

server := ginboot.New()
server.SetBasePath("/api/v1") // All routes will be prefixed with /api/v1

Route Groups

Ginboot allows you to organize routes into groups, which can share a common path prefix and middleware. You can create groups at the server level or nested within other ControllerGroups.

Controller-Level Groups

Ginboot strongly recommends that all routes are registered only within a Controller. Do not register routes directly on the server instance in main.go. Instead, use group.Group() inside your controller's Register method.

Nested Route Groups

You can create nested groups with shared middleware using the Group method on an existing ControllerGroup. This is perfect for versioning or applying specific middleware like authentication.

func (c *AdminController) Register(group *ginboot.ControllerGroup) {
    // Standard unauthenticated route
    group.GET("/ping", c.Ping)
    
    // Create a protected sub-group using middleware
    protected := group.Group("/v1", middleware.Auth(), middleware.AdminOnly())
    {
        protected.POST("/users", c.CreateUser)
        protected.PUT("/users/:id", c.UpdateUser)
        protected.DELETE("/users/:id", c.DeleteUser)
    }
}

HTTP Methods

ControllerGroup provides methods for all standard HTTP verbs:

group.GET("", handler)      // GET request
group.POST("", handler)     // POST request
group.PUT("", handler)      // PUT request
group.DELETE("", handler)   // DELETE request
group.PATCH("", handler)    // PATCH request
group.OPTIONS("", handler)  // OPTIONS request
group.HEAD("", handler)     // HEAD request

Path Parameters

Ginboot supports Gin's path parameter syntax, allowing you to capture values from the URL.

group.GET("/:id", controller.GetUser)           // Matches /users/123, :id captures "123"
group.GET("/:type/*path", controller.GetFile)   // Matches /files/image/avatar.png, :type captures "image", *path captures "avatar.png"

Handler Function Signatures

Ginboot offers flexibility in defining your handler functions. The framework's internal wrapHandler mechanism automatically adapts your handler's signature to Gin's requirements, handling request parsing, context injection, and error management. All handlers must return two values: a response value (can be any type) and an error value.

1. Context Only Handler

Use this pattern when your handler needs direct access to Ginboot's custom Context utilities (e.g., GetAuthContext, GetPageRequest, Param).

func (c *Controller) ListApiKeys(ctx *ginboot.Context) (*ApiKeyList, error) {
    authContext, err := ctx.GetAuthContext()
    if err != nil {
        return nil, err
    }
    pageRequest := ctx.GetPageRequest()
    // ... use authContext and pageRequest ...
    return &ApiKeyList{}, nil
}

2. Request Model Handler

This pattern is ideal when your handler primarily processes a request body. Ginboot will automatically parse and validate the request body into the provided struct.

type CreateApiKeyRequest struct {
    Name string `json:"name" binding:"required"`
}

func (c *Controller) CreateApiKey(request CreateApiKeyRequest) (*ApiKey, error) {
    // Request is automatically parsed and validated
    // Auth context can be accessed through middleware if needed
    return &ApiKey{}, nil
}

3. No Input Handler

For simple endpoints that don't require any input parameters or custom context, you can use this concise signature.

func (c *Controller) GetApiKeyStats() (*ApiKeyStats, error) {
    // Simple handlers with no input parameters
    return &ApiKeyStats{}, nil
}

4. Context and Request Model Handler

This pattern combines the benefits of both context and request model handlers, allowing access to ginboot.Context utilities and automatic request body parsing.

type UpdateUserRequest struct {
    Name string `json:"name" binding:"required"`
}

func (c *Controller) UpdateUser(ctx *ginboot.Context, request UpdateUserRequest) (*User, error) {
    userID := ctx.Param("id")
    // ... use userID from context and data from request ...
    return &User{}, nil
}

Middleware

Middleware can be applied at different levels to intercept requests and perform actions like authentication, logging, or data transformation.

Group Middleware

Apply middleware to an entire ControllerGroup when creating it. This is useful for protecting a set of routes with common logic, such as authentication.

import (
    "github.com/klass-lk/ginboot"
    "your-project/internal/middleware"
)

// Assuming middleware.Auth() and middleware.AdminOnly() are gin.HandlerFunc
adminGroup := server.Group("/admin", middleware.Auth(), middleware.AdminOnly())
{
    // All routes within this group will use Auth() and AdminOnly() middleware
    adminGroup.GET("/stats", adminController.GetStats)
    adminGroup.POST("/settings", adminController.UpdateSettings)
}

Route-Specific Middleware

You can also apply middleware to individual routes by passing them as additional arguments to the HTTP method functions.

import (
    "github.com/gin-gonic/gin"
    "github.com/klass-lk/ginboot"
    "your-project/internal/middleware"
)

// Assuming middleware.Cache() is a gin.HandlerFunc
group.GET("/users", middleware.Cache(), controller.ListUsers)

For server-wide middleware, refer to the Server Configuration Documentation.

On this page