Request Validation & Error Responses

Improper request validation causes silent data corruption and security risks. This lesson covers strict JSON decoding, body size limits, unknown field rejection, layered validation, and production-safe input handling.

On this page

Request Validation Is a Security Boundary

HTTP request validation is not a formality. It is a trust boundary. Every unvalidated field is a potential source of data corruption, undefined behavior, or security vulnerability.

Improper validation leads to:

  • Silent data loss
  • Mass assignment vulnerabilities
  • Denial-of-service attacks
  • Schema drift
  • Unexpected zero values

Real Production Failure: Silent JSON Field Drop

A service accepted JSON payloads for user updates. A frontend typo changed a field name. The Go server ignored unknown fields silently and stored incomplete data.

No error was returned. The bug propagated to analytics and billing systems.

Root cause: default JSON decoder ignores unknown fields.

Default JSON Decoding Is Unsafe

json.NewDecoder(r.Body).Decode(&payload)

This allows unknown fields silently.

Strict JSON Decoding

decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()

if err := decoder.Decode(&payload); err != nil {
    http.Error(w, "invalid payload", 400)
    return
}

DisallowUnknownFields prevents schema drift.

Limit Request Body Size

Without limits, attackers can send massive payloads.

r.Body = http.MaxBytesReader(w, r.Body, 1<<20)

This limits body to 1MB.

Without this, memory exhaustion attacks are trivial.

Check for Multiple JSON Objects

Ensure only one object is decoded.

if decoder.More() {
    http.Error(w, "multiple JSON objects not allowed", 400)
    return
}

Transport vs Domain Validation

Transport Validation

  • Required fields present
  • Field types correct
  • JSON structure valid

Domain Validation

  • Business rules
  • Cross-field constraints
  • Permission checks

Keep these layers separate.

Manual Validation Example

if payload.Email == "" {
    return errors.New("email required")
}
if len(payload.Password) < 8 {
    return errors.New("password too short")
}

Return structured errors when possible.

Numeric Overflow Risk

JSON numbers decode into float64 by default.

var data map[string]interface{}
json.Unmarshal(body, &data)

This loses integer precision for large values.

Prefer typed structs.

Streaming vs Buffering

Decoder streams input. Avoid reading full body into memory unnecessarily.

However, if you need to log raw payload, buffer carefully and limit size.

Content-Type Validation

if r.Header.Get("Content-Type") != "application/json" {
    http.Error(w, "unsupported content type", 415)
    return
}

Reject unexpected content types early.

Mass Assignment Risk

If struct contains sensitive fields, JSON binding may populate them unexpectedly.

type User struct {
    Email string
    IsAdmin bool
}

If IsAdmin is not validated, client may escalate privileges.

Separate input DTO from domain model.

Validation Libraries

Libraries like go-playground/validator help enforce struct tags.

type CreateUser struct {
    Email string `validate:"required,email"`
}

But understand validation logic — do not blindly trust tags.

Handling Partial Updates

PATCH endpoints require careful validation.

Distinguish between zero value and omitted field using pointers.

type UpdateUser struct {
    Email *string
}

DOS Vectors

  • Large JSON payloads
  • Deeply nested JSON objects
  • Slow request body streaming
  • Malformed Unicode

Combine ReadTimeout and MaxBytesReader.

Error Response Structure

Return structured validation errors:

{
  "error": "validation_failed",
  "fields": {
    "email": "invalid format"
  }
}

Do not leak internal error details.

Testing Validation

req := httptest.NewRequest("POST", "/users", bytes.NewReader(invalidJSON))
rr := httptest.NewRecorder()

handler.ServeHTTP(rr, req)

if rr.Code != 400 {
    t.Fatal("expected validation failure")
}

Test negative cases aggressively.

Common Anti-Patterns

  • Ignoring unknown fields
  • No body size limit
  • Binding directly into domain structs
  • Trusting client-provided IDs
  • Logging raw sensitive payloads

Operational Checklist

  • Body size limited
  • Unknown fields rejected
  • DTO separated from domain model
  • Content-Type validated
  • Numeric types defined explicitly
  • Validation tested with invalid inputs
  • Structured error responses implemented

Final Perspective

Request validation is the front door of your service. Silent acceptance of malformed input leads to cascading failures and security exposure. Strict decoding, size limits, and layered validation transform your HTTP layer into a reliable, defensive boundary suitable for production environments.