Binding & Validation
Binding — reading the request body
Section titled “Binding — reading the request body”In Express you access req.body (after express.json()). In Echo, c.Bind(&target) does the same: it reads the Content-Type header and decodes the body into the struct you pass. It also binds path params and query params if the struct fields carry the right tags.
// Express + TypeScriptinterface CreateBookDTO { title: string; author: string; year: number;}
app.post('/books', (req, res) => { const body = req.body as CreateBookDTO; // body.title, body.author, body.year available res.status(201).json(body);});// Echotype CreateBookRequest struct { Title string `json:"title"` Author string `json:"author"` Year int `json:"year"`}
func createBook(c echo.Context) error { var req CreateBookRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } // req.Title, req.Author, req.Year available return c.JSON(http.StatusCreated, req)}Struct tags — the annotation system
Section titled “Struct tags — the annotation system”Go struct tags are string literals attached to fields. They act like TypeScript decorators but without code execution — they are read at runtime via reflection. json:"name" controls serialization; validate:"rule" is read by a validator library.
// TypeScript class-validatorimport { IsString, IsInt, Min, IsNotEmpty } from 'class-validator';
class CreateBookDTO { @IsString() @IsNotEmpty() title: string;
@IsString() author: string;
@IsInt() @Min(1000) year: number;}// Go struct tagstype CreateBookRequest struct { Title string `json:"title" validate:"required"` Author string `json:"author" validate:"required"` Year int `json:"year" validate:"required,min=1000"`}Try it — struct tags with encoding/json
Section titled “Try it — struct tags with encoding/json”The stdlib encoding/json package reads json:"..." tags during Marshal/Unmarshal. This works in the playground:
package main
import ( "encoding/json" "fmt")
type Book struct { Title string `json:"title"` Author string `json:"author"` Year int `json:"year"` // omitempty: field is omitted from JSON when it is zero/empty ISBN string `json:"isbn,omitempty"`}
func main() { // Unmarshal (JSON -> struct) raw := `{"title":"The Go Programming Language","author":"Donovan & Kernighan","year":2015}` var b Book if err := json.Unmarshal([]byte(raw), &b); err != nil { fmt.Println("error:", err) return } fmt.Printf("Title: %s\n", b.Title) fmt.Printf("Author: %s\n", b.Author) fmt.Printf("Year: %d\n", b.Year) fmt.Printf("ISBN: %q (empty — omitted in output)\n", b.ISBN)
// Marshal (struct -> JSON) out, _ := json.MarshalIndent(b, "", " ") fmt.Println(string(out))}Loading Go runtime (first run only, ~8 MB)…
Validation with go-playground/validator
Section titled “Validation with go-playground/validator”Echo has no built-in validation, but it exposes e.Validator. The standard approach is to wire in go-playground/validator.
// NestJS global validation pipeimport { ValidationPipe } from '@nestjs/common';app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
// DTOclass CreateBookDTO { @IsString() @IsNotEmpty() title: string; @IsInt() @Min(1000) year: number;}// Wire validator once in main.goimport "github.com/go-playground/validator/v10"
type CustomValidator struct { v *validator.Validate}
func (cv *CustomValidator) Validate(i any) error { return cv.v.Struct(i)}
// In main():e.Validator = &CustomValidator{v: validator.New()}
// Handler — bind then validatefunc createBook(c echo.Context) error { var req CreateBookRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if err := c.Validate(req); err != nil { return echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error()) } // req is safe to use return c.JSON(http.StatusCreated, req)}