HTTP Responses
Learn how NLG Form binds and validates HTTP JSON requests and returns structured validation responses using map responses, flat responses, custom messages, invalid JSON handling, and unique error codes.
The httpform package provides helpers for validating HTTP JSON requests and returning consistent API responses when validation fails.
It combines three steps into one flow:
- Decode the incoming JSON request body
- Bind the payload into a Go struct
- Validate the struct using a
form.Schema
When validation fails, the helper writes the response automatically and returns false.
if !httpform.BindAndValidate(w, req, &in, ResponseExampleSchema(), 1<<20) {
return
}When validation succeeds, it returns true and the handler can continue.
The last argument defines the maximum allowed request body size. For example, 1<<20 limits the body to 1 MB.
Available Helpers
| Helper | Description |
|---|---|
BindAndValidate | Binds JSON, validates the input, and returns field-based validation errors |
BindAndValidateFlat | Binds JSON, validates the input, and returns validation errors as a flat list |
BindAndValidateWithOptions | Binds JSON, validates the input, and allows custom response format, messages, and unique error codes |
Response Options
Use ResponseOptions when you need custom API behavior.
opts := httpform.ResponseOptions{
ErrorFormat: httpform.ErrorFormatMap,
ValidationMessage: "please check your input",
InvalidJSONMessage: "request body is not valid JSON",
UniqueCodes: true,
}Available options:
| Option | Description |
|---|---|
ErrorFormat | Controls whether validation errors are returned as a map or flat list |
ValidationMessage | Custom top-level message for validation failures |
InvalidJSONMessage | Custom top-level message for malformed JSON requests |
UniqueCodes | Removes duplicated validation error codes per field |
Example Schema
package main
import (
"github.com/netlifeguru/form"
"github.com/netlifeguru/form/rules"
)
const (
CodeEmailRequired = form.Code("email_required")
CodePasswordRequired = form.Code("password_required")
CodePasswordMinLen = form.Code("password_min_len")
)
type ResponseExampleRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
func ResponseExampleSchema() form.Schema[ResponseExampleRequest] {
ResponseExampleForm := struct {
Email form.StringField[ResponseExampleRequest]
Password form.StringField[ResponseExampleRequest]
}{
Email: form.Str[ResponseExampleRequest]("email", func(r *ResponseExampleRequest) string {
return r.Email
}),
Password: form.Str[ResponseExampleRequest]("password", func(r *ResponseExampleRequest) string {
return r.Password
}),
}
EmailSchema := form.Schema[ResponseExampleRequest]{
rules.RequiredWithCode(ResponseExampleForm.Email, CodeEmailRequired),
rules.Email(ResponseExampleForm.Email),
}
PasswordSchema := form.Schema[ResponseExampleRequest]{
rules.RequiredWithCode(ResponseExampleForm.Password, CodePasswordRequired),
rules.MinLenWithCode(ResponseExampleForm.Password, 8, CodePasswordMinLen),
}
return form.Rules(
EmailSchema,
PasswordSchema,
)
}Map Response
BindAndValidate returns validation errors grouped by field name.
r.HandleFunc("/response-map", "POST", func(w http.ResponseWriter, req *http.Request, ctx *router.Context) {
var in ResponseExampleRequest
if !httpform.BindAndValidate(w, req, &in, ResponseExampleSchema(), 1<<20) {
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"message": "map response validation passed",
"data": in,
})
})This format is useful for frontend forms because each field can display its own validation errors.
Flat Response
BindAndValidateFlat returns validation errors as a flat list.
r.HandleFunc("/response-flat", "POST", func(w http.ResponseWriter, req *http.Request, ctx *router.Context) {
var in ResponseExampleRequest
if !httpform.BindAndValidateFlat(w, req, &in, ResponseExampleSchema(), 1<<20) {
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"message": "flat response validation passed",
"data": in,
})
})This format is useful for APIs that display or log validation errors as a single list.
Custom Response
BindAndValidateWithOptions allows you to customize the response format and messages.
opts := httpform.ResponseOptions{
ErrorFormat: httpform.ErrorFormatMap,
ValidationMessage: "please check your input",
InvalidJSONMessage: "request body is not valid JSON",
UniqueCodes: true,
}
if !httpform.BindAndValidateWithOptions(w, req, &in, ResponseExampleSchema(), 1<<20, opts) {
return
}Use this helper when your API needs consistent error messages, custom response formats, or deduplicated error codes.
Test Requests
The example can be tested with helper payloads:
validPayload := map[string]any{
"email": "john@example.com",
"password": "secret123",
}
invalidPayload := map[string]any{
"email": "invalid-email",
"password": "short",
}
emptyPayload := map[string]any{
"email": "",
"password": "",
}Example test calls:
form.SendTestPost(":8080/response-map", validPayload)
form.SendTestPost(":8080/response-map", invalidPayload)
form.SendTestPost(":8080/response-flat", invalidPayload)
form.SendTestPost(":8080/response-custom", emptyPayload)
form.SendTestPost(":8080/response-custom-flat", emptyPayload)Note
form.SendTestPostis a helper used only to make examples self-contained and easy to run locally. In real applications, requests usually come from frontend clients, API consumers, tests, or tools such ascurland Postman.
Complete Example
package main
import (
"encoding/json"
"fmt"
"log/slog"
"net/http"
"os"
"github.com/netlifeguru/form"
"github.com/netlifeguru/form/httpform"
"github.com/netlifeguru/router"
)
func main() {
r := router.New()
r.HandleFunc("/response-map", "POST", func(w http.ResponseWriter, req *http.Request, ctx *router.Context) {
var in ResponseExampleRequest
if !httpform.BindAndValidate(w, req, &in, ResponseExampleSchema(), 1<<20) {
fmt.Println("map response validation failed")
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"message": "map response validation passed",
"data": in,
})
})
r.HandleFunc("/response-flat", "POST", func(w http.ResponseWriter, req *http.Request, ctx *router.Context) {
var in ResponseExampleRequest
if !httpform.BindAndValidateFlat(w, req, &in, ResponseExampleSchema(), 1<<20) {
fmt.Println("flat response validation failed")
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"message": "flat response validation passed",
"data": in,
})
})
r.HandleFunc("/response-custom", "POST", func(w http.ResponseWriter, req *http.Request, ctx *router.Context) {
var in ResponseExampleRequest
opts := httpform.ResponseOptions{
ErrorFormat: httpform.ErrorFormatMap,
ValidationMessage: "please check your input",
InvalidJSONMessage: "request body is not valid JSON",
UniqueCodes: true,
}
if !httpform.BindAndValidateWithOptions(w, req, &in, ResponseExampleSchema(), 1<<20, opts) {
fmt.Println("custom response validation failed")
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"message": "custom response validation passed",
"data": in,
})
})
r.HandleFunc("/response-custom-flat", "POST", func(w http.ResponseWriter, req *http.Request, ctx *router.Context) {
var in ResponseExampleRequest
opts := httpform.ResponseOptions{
ErrorFormat: httpform.ErrorFormatFlat,
ValidationMessage: "please check your input",
InvalidJSONMessage: "request body is not valid JSON",
UniqueCodes: true,
}
if !httpform.BindAndValidateWithOptions(w, req, &in, ResponseExampleSchema(), 1<<20, opts) {
fmt.Println("custom flat response validation failed")
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"message": "custom flat response validation passed",
"data": in,
})
})
validPayload := map[string]any{
"email": "john@example.com",
"password": "secret123",
}
invalidPayload := map[string]any{
"email": "invalid-email",
"password": "short",
}
emptyPayload := map[string]any{
"email": "",
"password": "",
}
form.SendTestPost(":8080/response-map", validPayload)
form.SendTestPost(":8080/response-map", invalidPayload)
form.SendTestPost(":8080/response-flat", invalidPayload)
form.SendTestPost(":8080/response-custom", emptyPayload)
form.SendTestPost(":8080/response-custom-flat", emptyPayload)
if err := r.ListenAndServe(8080); err != nil {
slog.Error("failed to start server", "error", err)
os.Exit(1)
}
}Validation Helpers
The httpform package provides multiple validation helpers depending on the response format and customization level required by the application.
BindAndValidate
httpform.BindAndValidate(w, r, &in, PostSchema(), 1<<20)Binds the incoming JSON payload into a Go structure, validates it against the provided schema, and returns validation errors as a field-based map response.
Typical response format:
{
"message": "validation failed",
"errors": {
"email": [
{
"code": "email_invalid",
"message": "must be a valid email"
}
]
}
}This helper is typically used for:
- frontend forms
- field-level UI validation
- structured API validation responses
BindAndValidateFlat
httpform.BindAndValidateFlat(w, r, &in, PostSchema(), 1<<20)Behaves similarly to BindAndValidate, but returns validation errors as a flat list instead of grouping them by field.
Typical response format:
{
"message": "validation failed",
"errors": [
{
"field": "email",
"code": "email_invalid",
"message": "must be a valid email"
}
]
}This format is useful for:
- logging systems
- CLI tools
- flat API contracts
- validation aggregation pipelines
BindAndValidateWithOptions
httpform.BindAndValidateWithOptions(
w,
r,
&in,
PostSchema(),
1<<20,
opts,
)Provides full control over validation response formatting and behavior.
This helper allows:
- custom validation messages
- custom invalid JSON messages
- flat or map response formatting
- unique validation error codes
- response customization for API contracts
Example:
opts := httpform.ResponseOptions{
ErrorFormat: httpform.ErrorFormatMap,
ValidationMessage: "please check your input",
InvalidJSONMessage: "request body is not valid JSON",
UniqueCodes: true,
}Use this helper when the API requires standardized validation responses across services or frontend applications.
Validation Philosophy
Learn how NLG Form uses explicit, type-safe, reusable validation schemas instead of struct tags and reflection-heavy validation pipelines.
Response Customization
Learn how to customize validation responses in NLG Form using custom messages, unique error codes, and configurable HTTP validation response options.