Skip to content

Validating Inputs in a REST API

  • Input validation is the process of verifying that client-provided data meets expected formats, types, and constraints before processing it.
  • Without validation, REST APIs are vulnerable to security threats (e.g., SQL injection, command injection) and broken functionality.
  • The goal is to ensure that incoming requests (e.g., JSON payloads, query parameters) are secure and work as expected.
    • Example:
      • Valid: {"user_id": 123, "name": "Quacker"}
      • Invalid: {"user_id": "abc", "name": "##123@@"}

  1. Security: Protects against malicious data and injection attacks (e.g., SQL injection, command injection).
  2. Data integrity: Ensures inputs match expected types, formats, and constraints (e.g., valid email addresses, positive numbers).
  3. User experience: Provides clear error messages to API consumers when inputs are invalid.
  4. Reliability: Prevents crashes, runtime errors, and unexpected behavior from malformed data.

For a REST API, inputs can be provided by the client request in the following ways:

  • Query string parameters (e.g., GET /species?limit=10)
  • URL path parameters (e.g., GET /players/123)
  • Request body (e.g., JSON in a POST, PUT, or PATCH request)
  • Request headers (e.g., Accept, Authorization)

When processing user or client-provided data in a REST API, you’ll need to validate:

  • Presence: Is a required field provided?
  • Type: Is the input an integer, string, boolean, etc.?
  • Format: Does it match a pattern (e.g., UUID for resource IDs, ISO 8601 for timestamps, valid enum values for status fields)?
  • Range/Length: Is it within acceptable bounds (e.g., pagination limit 1-100, offset >= 0, price > 0)?
  • Business rules: Does it make sense in your domain (e.g., stock quantity available, valid status transitions)?

There are two main approaches to validating inputs in PHP:

  1. Native PHP functions:
    • filter_var() for sanitizing and validating common types (e.g., email, URL).
    • isset(), empty() for checking presence.
    • Type casting/checking with is_int(), is_string(), etc.
    • Example:
      $email = 'test@example.com';
      if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
      echo 'Invalid email format';
      }
      php
  2. Third-party libraries: Valitron: A simple, stand-alone validation library with no dependencies.

Resources:


Responding to Invalid Input.

  • HTTP Status: Use 400 Bad Request for validation failures.
  • Error Format: Return structured JSON with details.
  • Example Response:
    {
    "status": "error",
    "message": "Invalid input",
    "details": [
    {"field": "age", "message": "must be a positive number"},
    {"field": "email", "message": "invalid format"}
    ]
    }
    json
  • Best Practice: Be specific but avoid leaking internal details.

  • Inputs validation scenario: POST /players to create a player.
  • Validation Rules:
    • name: String, 2-50 chars, required.
    • age: Integer, 18-110, optional.
  • Code Example (PHP):
    $data = json_decode(file_get_contents('php://input'), true);
    $errors = [];
    // Validate name
    if (!isset($data['name']) || empty($data['name'])) {
    $errors[] = ['field' => 'name', 'message' => 'name is required'];
    } elseif (strlen($data['name']) < 2 || strlen($data['name']) > 50) {
    $errors[] = ['field' => 'name', 'message' => 'name must be between 2 and 50 characters'];
    }
    // Validate age (optional)
    if (isset($data['age']) && (!is_numeric($data['age']) || $data['age'] < 18 || $data['age'] > 110)) {
    $errors[] = ['field' => 'age', 'message' => 'age must be an integer between 18 and 110'];
    }
    if (!empty($errors)) {
    http_response_code(400);
    echo json_encode(['status' => 'error', 'message' => 'Validation failed', 'details' => $errors]);
    exit;
    }
    php

  1. Validate early: Check inputs at the controller or service layer.
  2. Use schemas: Define strict data models (e.g., JSON Schema, OpenAPI).
  3. Sanitize inputs: Remove or escape unsafe characters. Even after validation, use filter_var() or similar to sanitize before database insertion.
  4. Enforce types: Reject mismatched types (e.g., string instead of integer).
  5. Set boundaries: Limit lengths, ranges, and allowed values.
    • Example: "age": { "type": "integer", "minimum": 0, "maximum": 150 }
  6. Use HTTP Status Codes:
    • 400: Bad Request (malformed JSON).
    • 422: Unprocessable Entity (validation errors).
  7. Centralize Validation:
    • Move rules and messages to a separate class or helper class for reusability.
  8. Log Errors:
    • Log validation failures for debugging or security monitoring.
  9. Document Your API:
    • Use OpenAPI/Swagger to specify expected inputs and error responses.
  10. Handle Edge Cases:
    • Test with empty strings, null values, malformed JSON, and oversized payloads.