Introduzione alla Validazione dei Dati in TypeScript
Se sei un principiante in TypeScript, avrai notato che il linguaggio offre un sistema di tipi estremamente potente. Tuttavia, c'è un dettaglio fondamentale che ogni sviluppatore web deve comprendere: i tipi di TypeScript esistono solo durante la fase di compilazione. Una volta che il tuo codice viene trasformato in JavaScript per essere eseguito nel browser o su un server Node.js, tutte quelle definizioni di interface e type spariscono.
Immagina di ricevere una risposta da un'API esterna. Tu hai definito che l'utente ha un nome (stringa) e un'età (numero). Ma cosa succede se l'API, a causa di un errore, invia l'età come stringa o, peggio, non invia affatto il campo? TypeScript non può fermare l'errore a runtime perché non ha più il controllo sui dati. È qui che entra in gioco Zod.
Zod è una libreria di dichiarazione e validazione dello schema "TypeScript-first". A differenza di altre librerie, Zod ti permette di definire uno schema di validazione e, contemporaneamente, di estrarre automaticamente il tipo TypeScript da quello schema. Questo significa che non devi scrivere due volte la stessa struttura (una volta per la validazione e una volta per l'interfaccia), eliminando la ridondanza e riducendo drasticamente i bug.
Cos'è Zod e Perché Utilizzarlo?
Zod risolve il problema della "fiducia cieca" nei dati esterni. In un'applicazione web moderna, i dati arrivano da diverse fonti: form compilati dagli utenti, chiamate API, LocalStorage o database NoSQL come MongoDB. Nessuna di queste fonti è sicura al 100%.
I vantaggi principali di Zod
- Single Source of Truth: Definisci lo schema una volta e ottieni sia la validazione a runtime che il tipo statico.
- Zero Dipendenze: Zod è leggero e non richiede altre librerie per funzionare.
- Developer Experience (DX): Grazie all'integrazione con TypeScript, l'autocompletamento dell'IDE funziona perfettamente.
- Validazione Composta: Puoi creare schemi complessi, concatenando validazioni (es. una stringa che deve essere un'email e avere almeno 5 caratteri).
Senza Zod, saresti costretto a scrivere decine di istruzioni if (typeof data === 'string' && data.length > 0) per ogni singolo campo, rendendo il codice verboso e difficile da mantenere.
Installazione e Primi Passi
Per iniziare a usare Zod nel tuo progetto TypeScript, l'installazione è semplicissima. Apri il tuo terminale nella cartella del progetto e digita:
npm install zod
Una volta installato, possiamo creare il nostro primo schema. In Zod, uno "schema" è un oggetto che descrive la forma che i dati dovrebbero avere. Se i dati corrispondono allo schema, vengono accettati; altrimenti, Zod solleva un errore dettagliato.
Vediamo un esempio base di definizione di uno schema per un utente:
import { z } from 'zod';
// Definiamo lo schema per un utente
const UserSchema = z.object({
username: z.string().min(3, { message: "Il nome utente deve essere di almeno 3 caratteri" }),
email: z.string().email({ message: "L'email non è valida" }),
age: z.number().min(18, { message: "Devi avere almeno 18 anni" }),
isAdmin: z.boolean().optional(), // Questo campo è opzionale
});
// Estraggiamo il tipo TypeScript dallo schema
// Non abbiamo bisogno di scrivere 'interface User { ... }'
type User = z.infer<typeof UserSchema>;
console.log(User);
// Risultato: type User = { username: string; email: string; age: number; isAdmin?: boolean | undefined; }
In questo esempio, abbiamo usato z.object per definire un oggetto. Ogni proprietà ha un validatore specifico: .string(), .number(), .boolean(). Inoltre, abbiamo aggiunto dei vincoli come .min() e .email(), che permettono di validare non solo il tipo, ma anche il contenuto del dato.
Validazione dei Dati: parse vs safeParse
Zod offre due modi principali per validare i dati: .parse() e .safeParse(). Capire la differenza è fondamentale per gestire correttamente gli errori nell'interfaccia utente.
Il metodo .parse()
Il metodo .parse() è diretto. Se i dati sono validi, restituisce i dati validati. Se i dati sono invalidi, lancia un'eccezione (un errore) che interrompe l'esecuzione del codice a meno che non sia racchiuso in un blocco try...catch.
const result = UserSchema.parse({
username: "Mario",
email: "mario@esempio.it",
age: 25
});
// Se i dati sono corretti, 'result' è tipizzato come User
Il metodo .safeParse()
In un'applicazione web, lanciare eccezioni continue può essere rischioso o scomodo (specialmente nei form). .safeParse() non lancia errori, ma restituisce un oggetto che indica se l'operazione è riuscita o meno.
const dataToValidate = { username: "Ma", email: "non-email", age: 15 };
const validation = UserSchema.safeParse(dataToValidate);
if (!validation.success) {
// validation.error contiene tutti i dettagli sugli errori
console.log("Errore di validazione:", validation.error.format());
} else {
// validation.data contiene i dati validati e tipizzati
console.log("Dati validi:", validation.data);
}
L'utilizzo di safeParse è caldamente raccomandato quando si gestiscono input utente, poiché permette di mappare gli errori direttamente nei campi del form senza crashare l'applicazione.
Esempi Pratici di Integrazione
Caso 1: Validazione di una chiamata API
Uno dei casi d'uso più comuni è la validazione della risposta di un'API. Non possiamo fidarci di ciò che arriva dal server.
async function fetchUserProfile(id: string): Promise<User | null> {
const response = await fetch(`https://api.example.com/users/${id}`);
const rawData = await response.json();
const result = UserSchema.safeParse(rawData);
if (!result.success) {
console.error("L'API ha restituito dati non conformi:", result.error);
return null;
}
return result.data;
}
Caso 2: Gestione dei Form con React
Se utilizzi React, puoi integrare Zod per validare i campi di un form prima di inviarli al server. Questo riduce il carico sul backend e migliora l'esperienza utente fornendo feedback immediati.
import React, { useState } from 'react';
import { z } from 'zod';
const LoginFormSchema = z.object({
email: z.string().email("Email non valida"),
password: z.string().min(8, "La password deve essere di almeno 8 caratteri"),
});
export const LoginForm = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const result = LoginFormSchema.safeParse({ email, password });
if (!result.success) {
// Prendiamo il primo errore trovato
setError(result.error.errors[0].message);
return;
}
setError(null);
alert("Login effettuato con successo!");
};
return (
<form
<input value={email} => setEmail(e.target.value)} placeholder="Email" />
<input type="password" value={password} => setPassword(e.target.value)} placeholder="Password" />
{error && <p style={{ color: 'red' }}>{error}</p>}
<button type="submit">Accedi</button>
</form>
);
};
Errori Comuni e FAQ
Perché non usare solo le Interfacce di TypeScript?
Le interfacce sono "fantasma": spariscono dopo la compilazione. Se un'API invia un numero dove ti aspettavi una stringa, l'interfaccia non ti avviserà a runtime, e il tuo codice potrebbe crashare cercando di chiamare .toUpperCase() su un numero. Zod controlla i dati mentre l'app è in esecuzione.
Come gestire i campi opzionali?
In Zod, per rendere un campo opzionale, si usa .optional(). Se invece il campo può essere null ma deve essere presente, si usa .nullable(). È fondamentale distinguere tra undefined (campo assente) e null (campo presente ma vuoto).
Zod rallenta l'applicazione?
La validazione ha un costo computazionale, ma è infinitesimale rispetto al tempo di una chiamata di rete o al rendering di un componente React. Il beneficio in termini di stabilità e sicurezza supera di gran lunga l'impatto sulle performance.
Conclusione e Prossimi Passi
Zod trasforma il modo in cui gestiamo i dati in TypeScript, portando la sicurezza dei tipi dal mondo statico della compilazione a quello dinamico del runtime. Implementare la validazione degli schemi non è solo una questione di "pulizia del codice", ma una pratica di sicurezza essenziale per evitare bug critici e vulnerabilità.
Per approfondire:
- Zod Refinements: Impara a usare
.refine()per creare validazioni personalizzate (ad esempio, controllare che la password e la conferma password coincidano). - Integrazione con React Hook Form: Se sviluppi in React, prova la libreria
@hookform/resolvers, che permette di collegare Zod direttamente a React Hook Form per una gestione professionale dei form. - Zod Effects: Esplora
.transform()per modificare i dati durante la validazione (ad esempio, convertire una stringa di data in un oggettoDatedi JavaScript).
Inizia a integrare Zod nei tuoi progetti web oggi stesso: i tuoi futuri "io" (e i tuoi utenti) ti ringrazieranno per l'assenza di crash inaspettati!