Custom Request Validation with ozzo-validation in Go

Custom validation in Echo using ozzo-validation makes input handling much cleaner and more structured. Instead of manually checking each field, you can define validation rules declaratively. Let’s say we need to validate a form submission where each field has attributes like label, type, and is_required. package form_requests import ( "fmt" "regexp" validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/labstack/echo/v4" ) // FieldRequest defines a single form field type FieldRequest struct { Label string `json:"label"` Type string `json:"type"` IsRequired bool `json:"is_required"` } // CreateFormFieldsRequest wraps multiple fields type CreateFormFieldsRequest struct { Fields []FieldRequest `json:"fields"` } // BindAndValidate binds JSON data and runs validation func (r *CreateFormFieldsRequest) BindAndValidate(c echo.Context) []string { if err := c.Bind(r); err != nil { return []string{"Invalid request payload"} } err := validation.ValidateStruct(r, validation.Field(&r.Fields, validation.Required.Error("Fields array is required"), validation.Each( validation.By(validateField), )), ) return extractValidationErrors(err) } // validateField ensures each field meets the required rules func validateField(value interface{}) error { field, ok := value.(FieldRequest) if !ok { return fmt.Errorf("invalid field format") } return validation.ValidateStruct(&field, validation.Field(&field.Label, validation.Required.Error("Label is required"), validation.Length(3, 50).Error("Label must be between 3 and 50 characters"), validation.Match(regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)).Error("Label can only contain letters, numbers, underscores, and dashes"), ), validation.Field(&field.Type, validation.Required.Error("Type is required"), validation.In("text", "number", "email", "date", "checkbox").Error("Invalid field type"), ), validation.Field(&field.IsRequired, validation.Required.Error("IsRequired is required")), ) } // extractValidationErrors formats errors into a slice of strings func extractValidationErrors(err error) []string { if err == nil { return nil } var errors []string if ve, ok := err.(validation.Errors); ok { for _, e := range ve { errors = append(errors, e.Error()) } } return errors } Declarative Validation: Instead of manually checking fields, ozzo-validation provides a clean way to define rules. Custom Validation Function: validateField() ensures each field meets the required format and constraints. Consistent Error Handling: extractValidationErrors() collects and formats validation errors into a slice for easier debugging and response handling.

April 6, 2025 · 2 min · 295 words · Saqib Razzaq

Group Similar Routes in Go Echo

Using Echo groups is a great way to organize routes, especially when dealing with versioning, middleware, or related endpoints. Instead of cluttering the main file with multiple routes, you can logically group them. package main import ( "net/http" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) func main() { e := echo.New() // Global middleware e.Use(middleware.Logger()) e.Use(middleware.Recover()) // API versioning v1 := e.Group("/api/v1") // Group for user-related routes users := v1.Group("/users") users.GET("", getUsers) // GET /api/v1/users users.POST("", createUser) // POST /api/v1/users users.GET("/:id", getUserByID) // GET /api/v1/users/:id // Group for admin routes with middleware admin := v1.Group("/admin", adminMiddleware) admin.GET("/dashboard", adminDashboard) // GET /api/v1/admin/dashboard e.Logger.Fatal(e.Start(":8080")) } // Handlers func getUsers(c echo.Context) error { return c.JSON(http.StatusOK, map[string]string{"message": "List of users"}) } func createUser(c echo.Context) error { return c.JSON(http.StatusCreated, map[string]string{"message": "User created"}) } func getUserByID(c echo.Context) error { id := c.Param("id") return c.JSON(http.StatusOK, map[string]string{"message": "User ID: " + id}) } func adminDashboard(c echo.Context) error { return c.JSON(http.StatusOK, map[string]string{"message": "Admin dashboard"}) } // Middleware for admin routes func adminMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // Simulate an auth check if c.Request().Header.Get("Authorization") != "Bearer admin_token" { return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Unauthorized"}) } return next(c) } }

April 6, 2025 · 1 min · 187 words · Saqib Razzaq

Parse JSON to Struct in Go

Parsing JSON into a struct using json.Unmarshal is a fundamental task in Go when working with APIs or reading JSON files. The encoding/json package makes this process straightforward. Let’s say we receive the following JSON payload: { "name": "John Doe", "age": 30, "email": "johndoe@example.com" } We can parse this JSON into a struct like this: package main import ( "encoding/json" "fmt" ) type User struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email"` } func main() { jsonData := `{"name": "John Doe", "age": 30, "email": "johndoe@example.com"}` var user User err := json.Unmarshal([]byte(jsonData), &user) if err != nil { fmt.Println("Error parsing JSON:", err) return } fmt.Println("Parsed User Struct:", user) }

April 6, 2025 · 1 min · 111 words · Saqib Razzaq

Request Logger Middleware in Go Echo

Middleware in Echo allows us to intercept requests before they reach handlers, making it a great place to add logging. Echo provides a built-in logging middleware (middleware.Logger()) but for more control, we can create a custom middleware: package main import ( "log" "time" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) // Custom logging middleware func requestLogger(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { start := time.Now() req := c.Request() log.Printf("Request: method=%s, path=%s, remote=%s", req.Method, req.URL.Path, req.RemoteAddr) err := next(c) res := c.Response() log.Printf("Response: status=%d, duration=%s", res.Status, time.Since(start)) return err } } func main() { e := echo.New() // Use Echo's default logger middleware e.Use(middleware.Logger()) // Use custom logging middleware e.Use(requestLogger) e.GET("/", func(c echo.Context) error { return c.String(200, "Hello, Echo!") }) e.Start(":8080") }

April 6, 2025 · 1 min · 119 words · Saqib Razzaq