HTTP 422 Unprocessable Entity: REST API common

Cos'e HTTP 422 Unprocessable Entity

Il codice HTTP 422 Unprocessable Entity indica che il server ha capito perfettamente la richiesta dal punto di vista sintattico (e quindi NON e un 400), ma non puo elaborarla per ragioni semantiche, tipicamente errori di validazione del dominio applicativo. E uno dei codici più usati nelle API REST moderne.

422 vs 400 vs 409

CodiceQuando usarlo
400 Bad RequestJSON malformato, sintassi errata, encoding sbagliato
422 Unprocessable EntityJSON valido, ma email duplicata, password debole, campi mancanti
409 ConflictConflitto di stato (es. transizione illegale, race condition)

Esempio classico: validazione form

POST /api/users HTTP/1.1
Content-Type: application/json

{
  "name": "Mario",
  "email": "non-valida",
  "password": "123"
}

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "errors": {
    "email": ["L'email non e in un formato valido"],
    "password": [
      "Deve contenere almeno 8 caratteri",
      "Deve contenere almeno una lettera maiuscola"
    ]
  }
}

Struttura della risposta

Non esiste uno standard unico per il body, ma due convenzioni popolari:

JSON:API:

{
  "errors": [
    {
      "source": {"pointer": "/data/attributes/email"},
      "title": "Invalid format",
      "detail": "Email must be valid RFC 5322"
    }
  ]
}

Problem Details (RFC 7807):

{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation failed",
  "status": 422,
  "errors": {
    "email": ["Required field"]
  }
}

Implementazione Laravel

Laravel emette 422 automaticamente quando una FormRequest fallisce la validazione:

public function rules() {
    return [
        'email' => 'required|email|unique:users',
        'password' => 'required|min:8|confirmed',
    ];
}
// Se fallisce: 422 con JSON ben formato

Implementazione Express.js

const { body, validationResult } = require('express-validator');

app.post('/users',
    body('email').isEmail(),
    body('password').isLength({ min: 8 }),
    (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(422).json({ errors: errors.array() });
        }
        // ...
    }
);

Implementazione FastAPI

FastAPI usa Pydantic per validazione automatica e ritorna 422 di default:

from pydantic import BaseModel, EmailStr

class UserCreate(BaseModel):
    email: EmailStr
    password: str

@app.post("/users")
def create_user(user: UserCreate):
    # ...
# Se validazione fallisce: 422 automatico

WebDAV e 422

Storicamente il 422 era stato introdotto dalla RFC 4918 per WebDAV (XML semanticamente errato). Solo dalla RFC 9110 il suo uso e stato esplicitamente esteso a HTTP general purpose, formalizzando una pratica comune nelle API.

Localization dei messaggi

Le API multi-lingua dovrebbero localizzare i messaggi di errore:

GET /api/users HTTP/1.1
Accept-Language: it-IT

HTTP/1.1 422 Unprocessable Entity

{
  "errors": {
    "email": ["L'email non e valida"]
  }
}

422 vs 400 nei framework

Alcuni framework usano 400 per tutto, inclusi errori di validazione semantica. E una semplificazione accettabile per piccole API, ma 422 e più preciso:

  • 400: "Non capisco cosa mi stai mandando".
  • 422: "Capisco perfettamente, ma e sbagliato".

Per API consumate da client diversi (mobile, web, terzi), 422 facilita gestione errori dedicata.

Codici dettaglio

Per scalare oltre i messaggi testuali, includi codici machine-readable:

{
  "errors": [
    {
      "field": "email",
      "code": "EMAIL_ALREADY_EXISTS",
      "message": "Questa email e già registrata"
    },
    {
      "field": "password",
      "code": "PASSWORD_TOO_WEAK",
      "message": "La password e troppo debole"
    }
  ]
}

Così i client possono gestire programmaticamente specifici errori (es. mostrare il pulsante "Recupera password" se vede EMAIL_ALREADY_EXISTS).

Sicurezza: evitare information leakage

Nelle risposte 422 di endpoint sensibili (login, registrazione), evita di rivelare se un'email esiste:

// MALE: rivela esistenza
{"error": "Email già registrata"}

// BENE: ambiguo
{"error": "Combinazione email/password non valida"}

Test e debug

Postman e Insomnia mostrano chiaramente i body 422. In Chrome DevTools, vai in Network -> Preview per vedere il JSON formattato. Per parsing automatico in client, usa try/catch attorno alla validazione del body JSON.

FormRequest e validation rules

Laravel offre un sistema potente di validation rules: required, email, min:8, unique:users, confirmed, exists:roles,id, custom rules tramite classi. Quando una FormRequest fallisce, il framework emette automaticamente 422 con il dettaglio degli errori in JSON.

Internazionalizzazione errori

Le API devono restituire messaggi nella lingua del client (Accept-Language). Laravel localizza automaticamente via file resources/lang/{locale}/validation.php. FastAPI/Pydantic richiedono customizzazione esplicita. Importante per UX in app multi-lingua.

Field-level vs form-level errors

Errori specifici di un singolo campo (email invalida) sono diversi da errori che riguardano il form intero (password e conferma non corrispondono). Best practice JSON:

{
  "errors": {
    "_form": ["Password e conferma non corrispondono"],
    "email": ["Formato non valido"]
  }
}

422 e sicurezza

Quando un'endpoint protetto riceve dati invalidi, attenzione a non rivelare info: messaggio generico "credenziali invalide" invece di "email non esiste". Aiuta a prevenire enumeration attacks contro endpoint di auth.

422 in GraphQL

GraphQL non usa 422 nativo (sempre 200 con errors array nel body). Però molti gateway GraphQL davanti a REST translatano errori 422 in errors GraphQL field-level. Mantenere semantica simile per consistency.

422 con Pydantic strict

FastAPI con Pydantic strict mode rifiuta type coercion: se un campo e int e ricevi "42" string, errore 422. Strictness eleva qualità dei dati ma puo rompere client legacy. Trade-off: legato a quanto vuoi essere severo.

422 e cross-field validation

Validazione tra campi: data_inizio < data_fine, password = password_confirm, totale = somma(items). Errori cross-field vanno emessi anche con 422, ma in struttura _form o $root separata dai field-level.

Internazionalizzazione

I messaggi 422 dovrebbero rispettare Accept-Language. Server-side: lookup traduzione per ogni rule + field. Es. "email is required" in inglese diventa "L'email e obbligatoria" in italiano. Implementazione: gettext, intl, ICU MessageFormat.

Pagination errors

Errori 422 su parametri di paginazione (page=0 o page=-1): rifiuta esplicitamente con messaggio chiaro invece di silenzio o page=1 forzato. Aiuta debugging client SDK.

Hai bisogno di aiuto?

Se il tuo sito mostra errori HTTP, il team di G Tech Group puo aiutarti. Contattaci tramite il modulo di contatto.

Hai trovato utile quest'articolo?