Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
YouTube Security VideosMicrosoft Developer: Skill up on Copilot Studio Oct 8th!(24.09.2026 um 01:01 Uhr)
Sichere Programmierung🦄 Sharing DEV Followers Count on Github Profile 🦄(24.09.2026 um 00:42 Uhr)
Sichere ProgrammierungHow I Would Build a Private AI Coding Workstation in 2026(24.09.2026 um 00:46 Uhr)
Sichere ProgrammierungBuilding a TWAP Distance-Based Polymarket Trading Strategy(24.09.2026 um 00:50 Uhr)
Sichere ProgrammierungMy factual-recall tasks were scoring format, not facts(24.09.2026 um 01:15 Uhr)
YouTube Security VideosMicrosoft Developer: Skill up on Copilot Studio Oct 8th!(24.09.2026 um 01:01 Uhr)
Sichere Programmierung🦄 Sharing DEV Followers Count on Github Profile 🦄(24.09.2026 um 00:42 Uhr)
Sichere ProgrammierungHow I Would Build a Private AI Coding Workstation in 2026(24.09.2026 um 00:46 Uhr)
Sichere ProgrammierungBuilding a TWAP Distance-Based Polymarket Trading Strategy(24.09.2026 um 00:50 Uhr)
Sichere ProgrammierungMy factual-recall tasks were scoring format, not facts(24.09.2026 um 01:15 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Developing a Simple RESTful API with Gin, ginvalidator, and validatorgo

This tutorial introduces the basics of writing a RESTful web service API with Go and the Gin Web Framework (Gin). Which uses my two open source libraries: ginvalidator and validatorgo. ginvalidator is a lightweight and expressive…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

This tutorial introduces the basics of writing a RESTful web service API with Go and the Gin Web Framework (Gin). Which uses my two open source libraries: ginvalidator and validatorgo.





  • ginvalidator is a lightweight and expressive validation library for Go, designed specifically for the Gin web framework. It is based on the popular open source js library express-validator.


  • validatorgo is a versatile validation library for Go, offering a wide range of validators and sanitizers. It is based on the popular open source js library validator.js.



Please check out the above libraries and don’t forget to ⭐ star, 🔁 share, and 🍴 fork!






API endpoint design



You’ll build an API that provides access to a product inventory management system. So you’ll need to provide endpoints through which a HTTP client can create, read update and delete products.



When developing an API, you typically begin by designing the endpoints. Your API’s users will have more success if the endpoints are easy to understand.



Here are the endpoints you’ll create in this tutorial.





  • /products



    • GET - Get a list of all products, returned as JSON.


    • POST - Add a new product from request data and returned as JSON.







  • /products/:id



    • GET - Get a product by its id, returning the product data as JSON.


    • UPDATE - Get a product by its id, returning the newly updated data as JSON.


    • Delete - Deletes a product by its id.








To keep things simple for the tutorial, you’ll store data in memory. A more typical API would interact with a database. Note that storing data in memory means that the list of products will be lost each time you stop the server, then recreated when you start it.



Also all HTTP requests will be represented as .http code snippets. You can use any HTTP client, such as Postman, Insomnia, or the HTTP client in your IDE, to send your requests.






Writing the Code




  • Start by following the quick installation guide to set up the necessary dependencies.


  • Next, create a main.go file.


  • In the main.go file, define the following structs to model your product data: Dimensions, Supplier, and Product. The Dimensions and Supplier structs will be embedded within the Product struct.





  package main

import (
"time"
)

// Dimensions represents the physical dimensions of a product.
type Dimensions struct {
Length float64 `json:"length"` // Length of the product in centimeters
Width float64 `json:"width"` // Width of the product in centimeters
Height float64 `json:"height"` // Height of the product in centimeters
Weight float64 `json:"weight"` // Weight of the product in kilograms
}

// Supplier represents information about the supplier of a product.
type Supplier struct {
Name string `json:"name"` // Name of the supplier
Contact string `json:"contact"` // Contact details (e.g., email or phone number)
Address string `json:"address"` // Address of the supplier
}

// Product represents data about an inventory product.
type Product struct {
ID string `json:"id"` // Unique identifier for the product
Name string `json:"name"` // Name of the product
Category string `json:"category"` // Category the product belongs to
Description string `json:"description"` // Detailed description of the product
Price float64 `json:"price"` // Price of the product in decimal format
Stock int `json:"stock"` // Quantity of the product available in stock
Dimensions Dimensions `json:"dimensions"` // Physical dimensions of the product
Supplier Supplier `json:"supplier"` // Supplier details
Tags []string `json:"tags"` // Tags for searching and filtering
Image string `json:"image"` // URLs of product images
ManufacturedAt time.Time `json:"manufacturedAt"` // Timestamp when the product was manufactured
CreatedAt time.Time `json:"createdAt"` // Timestamp when the product was created
UpdatedAt time.Time `json:"updatedAt"` // Timestamp when the product was last updated
}







  • Then below add a slice products, with 2 items. This slice will be where our in memory database.




  var products = []Product{
{
ID: "p1",
Name: "Wireless Mouse",
Category: "Electronics",
Description: "A high-precision wireless mouse with ergonomic design.",
Price: 29.99,
Stock: 150,
Dimensions: Dimensions{
Length: 11.5,
Width: 6.0,
Height: 3.5,
Weight: 0.2,
},
Supplier: Supplier{
Name: "Tech Supplies Inc.",
Contact: "[email protected]",
Address: "123 Tech Street, Silicon Valley, CA",
},
Tags: []string{"wireless", "mouse", "electronics", "accessories"},
Image: "https://example.com/images/mouse1.jpg",
ManufacturedAt: time.Now().AddDate(0, -30, 0),
CreatedAt: time.Now().AddDate(0, -1, 0),
UpdatedAt: time.Now().AddDate(0, -1, 0),
},
{
ID: "p2",
Name: "Gaming Keyboard",
Category: "Electronics",
Description: "Mechanical keyboard with customizable RGB lighting.",
Price: 79.99,
Stock: 75,
Dimensions: Dimensions{
Length: 45.0,
Width: 15.0,
Height: 3.8,
Weight: 1.1,
},
Supplier: Supplier{
Name: "Gaming Gear Ltd.",
Contact: "[email protected]",
Address: "456 Gaming Road, Austin, TX",
},
Tags: []string{"keyboard", "gaming", "RGB", "electronics"},
Image: "https://example.com/images/keyboard2.jpg",
ManufacturedAt: time.Now().AddDate(0, -20, 0),
CreatedAt: time.Now().AddDate(0, -2, 0),
UpdatedAt: time.Now().AddDate(0, -2, 0),
},
}







  • Lets register a GET endpoint for /products in the main function. Here is the updated main function:




    func main() {
router := gin.Default()
router.GET("/products",
append(productQueriesValidators(), getProducts)...,
)
router.Run(":8080")
}






Below the main function, define the productQueriesValidators and getProducts functions:





  • productQueriesValidators: This function returns a gin.HandlersChain (equivalent to []gin.HandlerFunc).
    It includes two query validators for the GET /products route:



    • q: An optional query parameter. It will be validated only if present in the request, ensuring it's trimmed of leading and trailing whitespace and is not empty.


    • order: An optional query parameter. It will be trimmed of whitespace and must be one of asc or desc.









  func productQueriesValidators() gin.HandlersChain {
return gin.HandlersChain{
gv.NewQuery("q", nil).Chain().Optional().Trim(" ").Not().Empty(nil).Validate(),
gv.NewQuery("order", nil).Chain().Optional().Trim(" ").In([]string{"asc", "desc"}).Validate(),
}
}








  • getProducts: This is the final request handler middleware for the GET /products route. It performs the following steps:




    • Checks for validation errors using gv.ValidationResult.

    • If validation errors exist, it returns an HTTP 422 Unprocessable Entity status with the errors.

    • If no validation errors occur, it returns the product data with an HTTP 200 OK status.









  func getProducts(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": "The products found successfully",
"data": products,
"errors": nil,
})
}







Note we don't actually use the the search params (q, order) in the getProducts handler for the sake of simplicity.




Update the dependencies at the top of your Go file to include both net/http, gin and ginvalidator (aliased as gv):




  package main

import (
"fmt"
"net/http"
"time"

gv "github.com/bube054/ginvalidator"
"github.com/gin-gonic/gin"
)







  • Run and test the code for the GET /products endpoint.




  go run main.go






Here are examples of valid and invalid requests for testing:





  • A valid request.


    GET http://localhost:8080/products?q=electronics&order=asc HTTP/1.1
    content-type: application/json




  • An Invalid Request: order is neither asc nor desc.


    GET http://localhost:8080/products?q=&order=xyz HTTP/1.1
    content-type: application/json




  • An Invalid Request: q is empty


    GET http://localhost:8080/products?q=   &order=asc HTTP/1.1
    content-type: application/json




  • An Invalid Request: q is empty and order is neither asc nor desc


    GET http://localhost:8080/products?q=   &order=xyz HTTP/1.1
    content-type: application/json




    • Lets register a GET endpoint for /products/:id in the main function. Here is the updated main function:









    func main() {
router := gin.Default()
router.GET("/products",
append(productQueriesValidators(), getProducts)...,
)
router.GET("/products/:id",
append(productParamIdValidators(), getProduct)...,
)
router.Run(":8080")
}






Below the main function, define the productParamIdValidators and getProduct functions:





  • productParamIdValidators: It includes a param validator for id in /products/:id route:



    • id is simply the id for each product.









  func productParamIdValidators() gin.HandlersChain {
return gin.HandlersChain{gv.NewParam("id", nil).Chain().Trim(" ").Alphanumeric(nil).Validate()}
}








  • getProducts: This is the final request handler middleware for the /products route. It performs the following steps




    • Checks for validation errors using gv.ValidationResult.

    • If validation errors exist, it returns an HTTP 422 Unprocessable Entity status with the errors.

    • Retrieves matched data using gv.GetMatchedData.

    • Searches for the product by its id in the products list.

    • Return HTTP 404 if the product is not found and return HTTP 200 if the product is successfully found.









  func getProduct(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

data, err := gv.GetMatchedData(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

id, _ := data.Get(gv.ParamLocation, "id")

var product *Product

for _, p := range products {
if p.ID == id {
product = &p
break
}
}

if product == nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"message": fmt.Sprintf("Product with id %s, not found", id),
"data": nil,
"errors": nil,
})
return
} else {
c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": "The product found successfully",
"data": product,
"errors": nil,
})
return
}
}







  • Run and test the code for GET on /products/:id



Quit the previous process and rerun:




  go run main.go






A valid request.




  GET http://localhost:8080/products/p2 HTTP/1.1
content-type: application/json






Invalid request: id which is p2 is not alphanumeric




  GET http://localhost:8080/products/@#! HTTP/1.1
content-type: application/json







  • Lets register a DELETE endpoint for /products/:id in the main function. Here is the updated main function:




    func main() {
router := gin.Default()
router.GET("/products",
append(productQueriesValidators(), getProducts)...,
)
router.GET("/products/:id",
append(productParamIdValidators(), getProduct)...,
)
router.DELETE("/products/:id",
append(productParamIdValidators(), deleteProduct)...,
)
router.Run(":8080")
}






productParamIdValidators function will be reused.





  • deleteProduct: This is the final request handler middleware for the DELETE /products/:id route. It performs the following steps:


    • Validates the request parameters using gv.ValidationResult.

    • Returns an HTTP 422 status if validation errors are present.

    • Retrieves matched data using gv.GetMatchedData.

    • Searches for the product by its id in the products list.

    • If the product exists, its removed from the list and returns HTTP 200 with the deleted product details.

    • If the product does not exist, returns HTTP 404.









  func deleteProduct(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

data, err := gv.GetMatchedData(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": "The server encountered an unexpected error.",
"data": nil,
"errors": nil,
})
return
}

id, _ := data.Get(gv.ParamLocation, "id")

var deletedProduct *Product
var filteredProducts = []Product{}

for _, prod := range products {
if prod.ID == id {
deletedProduct = &prod
break
} else {
filteredProducts = append(filteredProducts, prod)
}
}

if deletedProduct == nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"message": fmt.Sprintf("Product with id %s, not found", id),
"data": nil,
"errors": nil,
})
return
}

products = filteredProducts
c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Product with id %s, deleted", id),
"data": deletedProduct,
"errors": nil,
})
}







  • Run and test the code for DELETE /products/:id



Quit the previous process and rerun:




  go run main.go






A valid request.




  DELETE http://localhost:8080/products/p2 HTTP/1.1
content-type: application/json






Invalid request: id which is p2 is not alphanumeric




  DELETE http://localhost:8080/products/@#! HTTP/1.1
content-type: application/json







  • Lets register a POST endpoint for /products in the main function. Here is the updated main function:




    func main() {
router := gin.Default()
router.GET("/products",
append(productQueriesValidators(), getProducts)...,
)
router.GET("/products/:id",
append(productParamIdValidators(), getProduct)...,
)
router.DELETE("/products/:id",
append(productParamIdValidators(), deleteProduct)...,
)
router.POST("/products",
append(productBodyValidators(), postProduct)...,
)
router.Run(":8080")
}








  • productBodyValidators: This function validates the request body for POST and PUT requests to the /products route. It validates:





    • name: Must not be empty.


    • category: Must belong to predefined categories (Electronics, Apparels, Groceries, Home-Appliances).


    • description: Length should be between 5 and 100 characters.


    • price: Must have at least 2 decimal places and use the en-US locale.


    • stock: Should be an integer, with a minimum value of 0.


    • dimensions:



      • length, width, height, weight: Must be numeric.







    • supplier:



      • name: Trimmed and not empty.


      • contact: Must be a valid email address.


      • address: Must match the regex format (number street, city, state abbreviation).







    • tags: Should be an array of strings.



    • image: Must be a valid URL.



    • manufacturedAt: Should follow the ISO8601 format and be after 2020-05-10T00:00:00Z.









  func productBodyValidators() gin.HandlersChain {
var (
minDesc uint = 5
maxDesc uint = 100
minStock int = 0
validCategories []string = []string{"Electronics", "Apparels", "Groceries", "Home-Appliances"}
)

return gin.HandlersChain{
gv.NewBody("name", nil).Chain().Not().Empty(nil).Validate(),
gv.NewBody("category", nil).Chain().In(validCategories).Validate(),
gv.NewBody("description", nil).Chain().Length(&vgo.IsLengthOpts{Min: uint(minDesc), Max: &maxDesc}).Validate(),
gv.NewBody("price", nil).Chain().Decimal(&vgo.IsDecimalOpts{DecimalDigits: vgo.DecimalDigits{Min: 2}, ForceDecimal: false, Locale: "en-US"}).Validate(),
gv.NewBody("stock", nil).Chain().Int(&vgo.IsIntOpts{Min: &minStock}).Validate(),
gv.NewBody(`dimensions.length`, nil).Chain().Numeric(nil).Validate(),
gv.NewBody("dimensions.width", nil).Chain().Numeric(nil).Validate(),
gv.NewBody("dimensions.height", nil).Chain().Numeric(nil).Validate(),
gv.NewBody("dimensions.weight", nil).Chain().Numeric(nil).Validate(),
gv.NewBody("supplier.name", nil).Chain().Trim(" ").Not().Empty(nil).Validate(),
gv.NewBody("supplier.contact", nil).Chain().Email(nil).Validate(),
gv.NewBody("supplier.address", nil).Chain().Matches(regexp.MustCompile(`^\d+\s[\w\s]+,\s[\w\s]+,\s[A-Z]{2}$`)).Validate(),
gv.NewBody("tags", nil).Chain().Array(nil).Validate(),
gv.NewBody("image", nil).Chain().URL(nil).Validate(),
gv.NewBody("manufacturedAt", nil).Chain().Date(&vgo.IsDateOpts{Format: vgo.ISO8601ZuluLayout, StrictMode: true}).After(&vgo.IsAfterOpts{ComparisonDate: "2020-05-10T00:00:00Z"}).Validate(),
}
}








  • postProduct: This is the final request handler middleware for the POST /products route. It performs the following steps:


    • Validates the request body using gv.ValidationResult.

    • Returns an HTTP 422 status if validation errors are present.

    • Binds the JSON request body to a Product struct.

    • Adds a unique ID, CreatedAt, and UpdatedAt fields to the new product.

    • Appends the new product to the products slice.

    • Returns HTTP 200 with the created product details.









  func postProduct(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

var newProduct Product

if err := c.BindJSON(&newProduct); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

newProduct.ID = fmt.Sprintf("p%d", len(products)+1)
newProduct.CreatedAt = time.Now()
newProduct.UpdatedAt = newProduct.CreatedAt

products = append(products, newProduct)
c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Product with id %s, has been created", newProduct.ID),
"data": newProduct,
"errors": nil,
})
}






Update the dependencies at the top of your Go file to include validatorgo (aliased as vgo):




  package main

import (
"fmt"
"net/http"
"regexp"
"time"

gv "github.com/bube054/ginvalidator"
vgo "github.com/bube054/validatorgo"
"github.com/gin-gonic/gin"
)







  • Run and test the code for POST /products



Quit the previous process and rerun:




  go run main.go






A valid request.




  POST http://localhost:8080/products HTTP/1.1
content-type: application/json

{
"name": "Ultra HD Monitor",
"category": "Electronics",
"description": "27-inch 4K Ultra HD monitor with HDR support and ultra-slim bezels.",
"price": 345.99,
"stock": 30,
"dimensions": {
"length": 61.0,
"width": 36.0,
"height": 5.0,
"weight": 4.5
},
"supplier": {
"name": "VisionTech Co.",
"contact": "[email protected]",
"address": "123 Display Lane, San Francisco, CA"
},
"tags": ["monitor", "4K", "HDR", "electronics", "display"],
"image": "https://example.com/images/monitor1.jpg",
"manufacturedAt": "2023-09-11T11:34:56Z"
}






An invalid request.




  POST http://localhost:8080/products HTTP/1.1
content-type: application/json

{
"name": "",
"category": "Equipment",
"description": "four",
"price": 345.99,
"stock": 30,
"dimensions": {},
"supplier": {
"name": "",
"contact": "123 Display Lane, San Francisco, CA",
"address": "[email protected]",
},
"tags": null,
"image": "image-here",
"manufacturedAt": "2019-09-11T11:34:56Z"
}







  • Lets register a PUT endpoint for /products/:id in the main function. Here is the updated main function:




    func main() {
router := gin.Default()
router.GET("/products",
append(productQueriesValidators(), getProducts)...,
)
router.GET("/products/:id",
append(productParamIdValidators(), getProduct)...,
)
router.DELETE("/products/:id",
append(productParamIdValidators(), deleteProduct)...,
)
router.POST("/products",
append(productBodyValidators(), postProduct)...,
)
router.PUT("/products/:id",
append(append(productParamIdValidators(), productBodyValidators()...), putProducts)...,
)
router.Run(":8080")
}






We will re-use productParamIdValidators and productBodyValidators functions.





  • putProduct: This is the final request handler middleware for the POST /products route. It performs the following steps:


    • Validates the request parameters and body: Uses gv.ValidationResult to check for validation errors in both the route parameters and the request body.

    • Retrieves matched data using gv.GetMatchedData.

    • Finds the product: Matches the provided id with the existing products.

    • Updates the product: If the product is found, it updates its details and sets a new UpdatedAt timestamp.

    • It returns HTTP 200 with the updated product details if successful.

    • It returns HTTP 404 if the product is not found.

    • It returns HTTP 422 for validation errors.









  func putProducts(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

var newProduct Product

if err := c.BindJSON(&newProduct); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

data, err := gv.GetMatchedData(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": "The server encountered an unexpected error.",
"data": nil,
"errors": nil,
})
return
}

id, _ := data.Get(gv.ParamLocation, "id")

var EditedProduct *Product
var newProducts = make([]Product, len(products))

for ind, prod := range products {
if prod.ID == id {
newProduct.ID = id
newProduct.CreatedAt = prod.CreatedAt
newProduct.UpdatedAt = time.Now()
newProducts[ind] = newProduct
EditedProduct = &newProduct
} else {
newProducts[ind] = prod
}
}

if EditedProduct == nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"message": fmt.Sprintf("Product with id %s, not found", id),
"data": nil,
"errors": nil,
})
return
}

products = newProducts
c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Product with id %s, has been edited", id),
"data": EditedProduct,
"errors": nil,
})
}






Run and test the code for PUT /products



Quit the previous process and rerun:




  go run main.go






A valid request.




  PUT http://localhost:8080/products HTTP/1.1
content-type: application/json

{
"name": "Ultra 8k Monitor",
"category": "Electronics",
"description": "27-inch 8K Ultra HD monitor with HDR support and ultra-slim bezels.",
"price": 390.99,
"stock": 15,
"dimensions": {
"length": 16.0,
"width": 63.0,
"height": 0.5,
"weight": 4.5
},
"supplier": {
"name": "VisionTech Co.",
"contact": "[email protected]",
"address": "123 Display Lane, San Francisco, CA"
},
"tags": ["8K", "HDR", "electronics", "display"],
"image": "https://example.com/images/monitor1-edited.jpg",
"manufacturedAt": "2023-09-11T11:34:56Z"
}






An invalid request.




  PUT http://localhost:8080/products HTTP/1.1
content-type: application/json

{
"name": "",
"category": "Equipment",
"description": "four",
"price": 345.99,
"stock": 30,
"dimensions": null,
"supplier": {
"name": "",
"contact": "123 Display Lane, San Francisco, CA",
"address": "[email protected]",
},
"tags": null,
"image": "image-here",
"manufacturedAt": "2019-09-11T11:34:56Z"
}









Final thoughts



Now you can see how straightforward it is to validate your HTTP requests in Gin using both ginvalidator and validatorgo.



For more details on customizing error messages, check out this guide. To learn more about field syntax extraction, refer to this section.



If you prefer not to use the append method in the route registration handlers, you can break down the middleware validator handlers into smaller chunks and add them individually to the registration handler.



Thanks for reading, and see you next time!






Full Code






package main

import (
"fmt"
"net/http"
"regexp"
"time"

gv "github.com/bube054/ginvalidator"
vgo "github.com/bube054/validatorgo"
"github.com/gin-gonic/gin"
)

// Dimensions represents the physical dimensions of a product.
type Dimensions struct {
Length float64 `json:"length"` // Length of the product in centimeters
Width float64 `json:"width"` // Width of the product in centimeters
Height float64 `json:"height"` // Height of the product in centimeters
Weight float64 `json:"weight"` // Weight of the product in kilograms
}

// Supplier represents information about the supplier of a product.
type Supplier struct {
Name string `json:"name"` // Name of the supplier
Contact string `json:"contact"` // Contact details (e.g., email or phone number)
Address string `json:"address"` // Address of the supplier
}

// Product represents data about an inventory product.
type Product struct {
ID string `json:"id"` // Unique identifier for the product
Name string `json:"name"` // Name of the product
Category string `json:"category"` // Category the product belongs to
Description string `json:"description"` // Detailed description of the product
Price float64 `json:"price"` // Price of the product in decimal format
Stock int `json:"stock"` // Quantity of the product available in stock
Dimensions Dimensions `json:"dimensions"` // Physical dimensions of the product
Supplier Supplier `json:"supplier"` // Supplier details
Tags []string `json:"tags"` // Tags for searching and filtering
Image string `json:"image"` // URLs of product images
ManufacturedAt time.Time `json:"manufacturedAt"` // Timestamp when the product was manufactured
CreatedAt time.Time `json:"createdAt"` // Timestamp when the product was created
UpdatedAt time.Time `json:"updatedAt"` // Timestamp when the product was last updated
}

var products = []Product{
{
ID: "p1",
Name: "Wireless Mouse",
Category: "Electronics",
Description: "A high-precision wireless mouse with ergonomic design.",
Price: 29.99,
Stock: 150,
Dimensions: Dimensions{
Length: 11.5,
Width: 6.0,
Height: 3.5,
Weight: 0.2,
},
Supplier: Supplier{
Name: "Tech Supplies Inc.",
Contact: "[email protected]",
Address: "123 Tech Street, Silicon Valley, CA",
},
Tags: []string{"wireless", "mouse", "electronics", "accessories"},
Image: "https://example.com/images/mouse1.jpg",
ManufacturedAt: time.Now().AddDate(0, -30, 0),
CreatedAt: time.Now().AddDate(0, -1, 0),
UpdatedAt: time.Now().AddDate(0, -1, 0),
},
{
ID: "p2",
Name: "Gaming Keyboard",
Category: "Electronics",
Description: "Mechanical keyboard with customizable RGB lighting.",
Price: 79.99,
Stock: 75,
Dimensions: Dimensions{
Length: 45.0,
Width: 15.0,
Height: 3.8,
Weight: 1.1,
},
Supplier: Supplier{
Name: "Gaming Gear Ltd.",
Contact: "[email protected]",
Address: "456 Gaming Road, Austin, TX",
},
Tags: []string{"keyboard", "gaming", "RGB", "electronics"},
Image: "https://example.com/images/keyboard2.jpg",
ManufacturedAt: time.Now().AddDate(0, -20, 0),
CreatedAt: time.Now().AddDate(0, -2, 0),
UpdatedAt: time.Now().AddDate(0, -2, 0),
},
}

func main() {
router := gin.Default()
router.GET("/products",
append(productQueriesValidators(), getProducts)...,
)
router.GET("/products/:id",
append(productParamIdValidators(), getProduct)...,
)
router.DELETE("/products/:id",
append(productParamIdValidators(), deleteProduct)...,
)
router.POST("/products",
append(productBodyValidators(), postProduct)...,
)
router.PUT("/products/:id",
append(append(productParamIdValidators(), productBodyValidators()...), putProducts)...,
)
router.Run(":8080")
}

func productQueriesValidators() gin.HandlersChain {
return gin.HandlersChain{
gv.NewQuery("q", nil).Chain().Optional().Trim(" ").Not().Empty(nil).Validate(),
gv.NewQuery("order", nil).Chain().Optional().Trim(" ").In([]string{"asc", "desc"}).Validate(),
}
}

func productParamIdValidators() gin.HandlersChain {
return gin.HandlersChain{gv.NewParam("id", nil).Chain().Trim(" ").Alphanumeric(nil).Validate()}
}

func productBodyValidators() gin.HandlersChain {
var (
minDesc uint = 5
maxDesc uint = 100
minStock int = 0
validCategories []string = []string{"Electronics", "Apparels", "Groceries", "Home-Appliances"}
)

return gin.HandlersChain{
gv.NewBody("name", nil).Chain().Not().Empty(nil).Validate(),
gv.NewBody("category", nil).Chain().In(validCategories).Validate(),
gv.NewBody("description", nil).Chain().Length(&vgo.IsLengthOpts{Min: uint(minDesc), Max: &maxDesc}).Validate(),
gv.NewBody("price", nil).Chain().Decimal(&vgo.IsDecimalOpts{DecimalDigits: vgo.DecimalDigits{Min: 2}, ForceDecimal: false, Locale: "en-US"}).Validate(),
gv.NewBody("stock", nil).Chain().Int(&vgo.IsIntOpts{Min: &minStock}).Validate(),
gv.NewBody(`dimensions.length`, nil).Chain().Numeric(nil).Validate(),
gv.NewBody("dimensions.width", nil).Chain().Numeric(nil).Validate(),
gv.NewBody("dimensions.height", nil).Chain().Numeric(nil).Validate(),
gv.NewBody("dimensions.weight", nil).Chain().Numeric(nil).Validate(),
gv.NewBody("supplier.name", nil).Chain().Trim(" ").Not().Empty(nil).Validate(),
gv.NewBody("supplier.contact", nil).Chain().Email(nil).Validate(),
gv.NewBody("supplier.address", nil).Chain().Matches(regexp.MustCompile(`^\d+\s[\w\s]+,\s[\w\s]+,\s[A-Z]{2}$`)).Validate(),
gv.NewBody("tags", nil).Chain().Array(nil).Validate(),
gv.NewBody("image", nil).Chain().URL(nil).Validate(),
gv.NewBody("manufacturedAt", nil).Chain().Date(&vgo.IsDateOpts{Format: vgo.ISO8601ZuluLayout, StrictMode: true}).After(&vgo.IsAfterOpts{ComparisonDate: "2020-05-10T00:00:00Z"}).Validate(),
}
}

func getProducts(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": "The products found successfully",
"data": products,
"errors": nil,
})
}

func getProduct(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

data, err := gv.GetMatchedData(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

id, _ := data.Get(gv.ParamLocation, "id")

var product *Product

for _, p := range products {
if p.ID == id {
product = &p
break
}
}

if product == nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"message": fmt.Sprintf("Product with id %s, not found", id),
"data": nil,
"errors": nil,
})
return
} else {
c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": "The product found successfully",
"data": product,
"errors": nil,
})
return
}
}

func deleteProduct(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

data, err := gv.GetMatchedData(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": "The server encountered an unexpected error.",
"data": nil,
"errors": nil,
})
return
}

id, _ := data.Get(gv.ParamLocation, "id")

var deletedProduct *Product
var filteredProducts = []Product{}

for _, prod := range products {
if prod.ID == id {
deletedProduct = &prod
break
} else {
filteredProducts = append(filteredProducts, prod)
}
}

if deletedProduct == nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"message": fmt.Sprintf("Product with id %s, not found", id),
"data": nil,
"errors": nil,
})
return
}

products = filteredProducts
c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Product with id %s, deleted", id),
"data": deletedProduct,
"errors": nil,
})
}

func postProduct(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

var newProduct Product

if err := c.BindJSON(&newProduct); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

newProduct.ID = fmt.Sprintf("p%d", len(products)+1)
newProduct.CreatedAt = time.Now()
newProduct.UpdatedAt = newProduct.CreatedAt

products = append(products, newProduct)
c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Product with id %s, has been created", newProduct.ID),
"data": newProduct,
"errors": nil,
})
}

func putProducts(c *gin.Context) {
result, err := gv.ValidationResult(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

if len(result) != 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"message": "The server encountered validation error",
"data": nil,
"errors": result,
})
return
}

var newProduct Product

if err := c.BindJSON(&newProduct); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": fmt.Sprintf("The server encountered an unexpected error: %v", err),
"data": nil,
"errors": nil,
})
return
}

data, err := gv.GetMatchedData(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": "The server encountered an unexpected error.",
"data": nil,
"errors": nil,
})
return
}

id, _ := data.Get(gv.ParamLocation, "id")

var EditedProduct *Product
var newProducts = make([]Product, len(products))

for ind, prod := range products {
if prod.ID == id {
newProduct.ID = id
newProduct.CreatedAt = prod.CreatedAt
newProduct.UpdatedAt = time.Now()
newProducts[ind] = newProduct
EditedProduct = &newProduct
} else {
newProducts[ind] = prod
}
}

if EditedProduct == nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"message": fmt.Sprintf("Product with id %s, not found", id),
"data": nil,
"errors": nil,
})
return
}

products = newProducts
c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("Product with id %s, has been edited", id),
"data": EditedProduct,
"errors": nil,
})
}


CTI Threat Relationship Graph2 Knoten / 1 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
IR-PLAYBOOK-RCE
HIGH
SOC Incident Playbook: Remote Code Execution (RCE) Defense
1-Click Detection Engineering: Sigma & YARA Rules
SOC Ready
title: Detect Exploitation - Developing a Simple RESTful API with Gin, ginvalidator, and validatorgo
id: ee91e9cb-f6b1-4a9e-8eb7-2437c0c35d6d
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-24
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-24"
        description = "YARA Signature for "
    strings:
        $str = "Developing a Simple RESTful AP" ascii wide
    condition:
        any of them
}
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Developing a Simple RESTful API with Gin, ginvalidator, and validatorgo

Thematisch verwandte Begriffe: Developing, Simple, RESTful, with · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-96550 | A vulnerability was found in sfturing hosp_order up to 627f426331da8086c…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick