Request Validation Patterns

Production-grade guide to request validation patterns covering architecture patterns, implementation strategies, testing approaches, and operational best practices for enterprise engineering teams.

Request validation is the first line of defense for any backend service that accepts inbound traffic. It sits between the raw HTTP layer and your business logic, ensuring that malformed, malicious, or unexpected data never reaches processing paths where it could corrupt state, leak information, or trigger denial-of-service conditions. This page covers the practical ways to structure validation so that it is repeatable, observable, and resistant to the common gaps that appear in production systems.

Validate JSON Structure and Types

The most frequent validation task is confirming that a request body conforms to the expected shape before any business logic runs. A concrete starting point is observing what happens when a client sends a payload that violates type expectations.

curl -s -X POST http://api.example.com/orders \
  -H "Content-Type: application/json" \
  -d '{"qty": -5, "id": "abc123"}' \
  -w "\nHTTP Status: %{http_code}\n"

Typical output:

HTTP Status: 400

The 400 status indicates that the server rejected the request before it reached routing or processing. Many frameworks return 422 Unprocessable Entity when the payload shape is correct but values are invalid (e.g., a negative quantity where only positive integers are allowed). The distinction matters: 400 often signals a malformed request format, while 422 signals a valid format with invalid content.

A minimal validation function that can be dropped into any language runtime without external dependencies might look like this:

def validate_payload(data, rules):
    errors = []
    for field, rule in rules.items():
        if rule.get("required") and field not in data:
            errors.append(f"{field} is required")
            continue
        if field in data and "type" in rule:
            expected = rule["type"]
            if expected == "integer" and not isinstance(data[field], int):
                errors.append(f"{field} must be integer")
            elif expected == "string" and not isinstance(data[field], str):
                errors.append(f"{field} must be string")
            elif expected == "number" and not isinstance(data[field], (int, float)):
                errors.append(f"{field} must be number")
    if errors:
        return False, "; ".join(errors)
    return True, None

When called with {"qty": -5} and rules { "qty": {"required": true, "type": "integer"}, "id": {"required": true, "type": "string"} }, the function returns (False, "qty must be integer"). The caller can then compose a 422 response with those details, or a 400 if the payload itself cannot be parsed.

Validate Against a Schema Registry

Beyond manual field checks, many teams centralize validation logic in a schema store. A common pattern involves sending the raw body to a validation endpoint that compares it against a stored JSON Schema or OpenAPI fragment. The curl interface to such a service typically follows this form:

curl -s -X POST http://validate.example.com/check \
  -H "Content-Type: application/json" \
  -d @payload.json \
  -o response.body \
  -w "HTTP Status: %{http_code}\n"

If the payload conforms, the service returns 200 OK; if it does not, it returns 400 Bad Request with a JSON body listing each violated keyword (e.g., {"error": "missing required field \"email\""}). The -o response.body flag captures that diagnostic payload for logging or alerting.

A real jq one-liner can validate a local file against a minimal schema expressed as a filter:

jq 'if has("email") and (.email | test("@")) then true else error("validation failed") end' <<< '{"email": "[email protected]"}'

Output:

true

Attempting the same against a malformed payload:

jq 'if has("email") and (.email | test("@")) then true else error("validation failed") end' <<< '{"email": "invalid"}'

Produces:

jq: error: Regex: Compilation error(s) at :1.1-1.13: quantifier operand must be a string or a character class for test; ...
If you need more help, see: https://jqlang.github.io/manual/

This demonstrates how jq can serve as a lightweight on‑demand validator in scripts or CI steps, without pulling in a dedicated schema‑validation library.

When a request slips past application‑level validation and reaches the database, a downstream constraint violation may surface as a 23505 (unique constraint violation) or 22P02 (invalid input syntax for type) error, depending on the PostgreSQL error class. These are not validation failures in the HTTP sense, but they underscore the importance of aligning application‑level checks with database schemas.

Validate Headers, Content‑Type, and Authentication Credentials

Body shape is only one vector. Headers and auth tokens are equally prone to omission or corruption.

curl -s -I http://api.example.com/data

Typical output:

HTTP/1.1 415 Unsupported Media Type
Content-Type: text/plain
Content-Length: 19
Date: Tue, 17 Sep 2025 12:34:56 GMT
Server: example/1.0

A 415 response indicates that the Content-Type header does not match what the endpoint expects. Omitting the header entirely, sending text/plain for a JSON endpoint, or providing application/xml when only JSON is supported will all trigger this rejection.

For authentication‑adjacent validation, the Authorization header is the most common entry point:

curl -s -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" \
  http://api.example.com/protected

If the token is malformed, expired, or signed with a key the server does not recognize, the backend typically returns 401 Unauthorized with a WWW-Authenticate challenge, or 403 Forbidden if the token is valid but the associated principal lacks the required scope. A frequent operational surprise is a 200 OK response when a reverse proxy strips the Authorization header before the request reaches the app, effectively bypassing the check entirely.

Sharp Edges — What Silently Fails

Several patterns lull operators into a false sense of security.

Common Confusions

Key Takeaways

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.