Condividere i tipi tra quattro app
10 aprile 2026

ReD Sposi è quattro applicazioni che si parlano:
- App mobile — Expo + React Native, Expo Router, NativeWind
- Backend API — Express + Node.js, MongoDB con Mongoose
- Pannello admin — React + Vite, SPA
- Landing page — Next.js
Tutte e quattro usano TypeScript. Tutte e quattro parlano con le stesse API. Tutti e quattro modellano le stesse entità: invitati, RSVP, foto, messaggi di chat, scommesse.
La domanda inevitabile è: come tieni allineati i tipi?
Le opzioni sul tavolo
Esistono tre approcci principali.
Package condiviso. Crei un package @red-sposi/types nel monorepo, ci metti tutte le interfacce condivise, e ogni app lo importa come dipendenza. È l'approccio più pulito architetturalmente — un'unica fonte di verità, nessuna duplicazione.
Validazione runtime con Zod. Definisci gli schemi con Zod, e da ogni schema inferisci il tipo TypeScript. Il vantaggio è che la validazione avviene a runtime — se il backend manda un campo in più o in meno, lo sai immediatamente, non solo al compile time.
Copy-paste consapevole. Definisci i tipi dove ha più senso (di solito sul backend, vicino ai modelli Mongoose), e li copi manualmente nelle app che li usano. È tecnicamente il meno elegante, ma ha zero overhead di configurazione.
Cosa ho scelto e perché
Per ReD Sposi ho scelto una via di mezzo tra il package condiviso e il copy-paste: tipi definiti sul backend, esportati da un file dedicato, e copiati manualmente nel frontend quando cambiano.
Non è la scelta più elegante. È la scelta con il costo di setup più basso per un progetto con una scadenza fissa, costruito da una persona sola.
Un package condiviso richiede configurazione del monorepo — path aliases, build pipeline, risoluzione dei moduli in Expo e Next.js. Expo in particolare ha alcune limitazioni su come risolve i moduli esterni al suo root, che richiedono configurazione aggiuntiva nel metro.config.js e nel tsconfig.json. Non è impossibile, ma è un'ora di setup che non aggiunge funzionalità.
Zod è ottimo — lo uso sul backend per validare i body delle richieste — ma usarlo come source of truth per i tipi condivisi aggiunge un layer di complessità che per questo progetto non si ripagava.
Come funziona in pratica
Sul backend, i tipi vivono vicino ai modelli Mongoose:
// models/Guest.ts
export interface IGuest {
_id: string;
name: string;
code: string;
rsvp?: {
confirmed: boolean;
menuChoice: "standard" | "vegetarian" | "vegan";
shuttle: boolean;
completedAt: Date;
};
pushToken?: string;
}
const GuestSchema = new Schema<IGuest>({ ... });
export const Guest = model<IGuest>("Guest", GuestSchema);
Sul frontend, importo direttamente questo tipo o ne definisco uno derivato per le esigenze specifiche dell'UI:
// app mobile: tipi locali che rispecchiano il backend
export interface Guest {
_id: string;
name: string;
rsvp?: {
confirmed: boolean;
menuChoice: "standard" | "vegetarian" | "vegan";
shuttle: boolean;
};
}
Non è DRY nel senso stretto. Ma per un'app con una decina di entità principali e una vita di qualche mese, è sufficiente.
Il vero problema: la deriva dei tipi
La criticità non è nel setup — è nel mantenimento. I tipi si desincronizzano quando cambiano i modelli del backend e il frontend non viene aggiornato.
In pratica questo si manifesta in due modi: errori TypeScript che non spiegano chiaramente cosa è cambiato, o — peggio — nessun errore perché il tipo è any o la risposta dell'API non viene validata.
Ho mitigato questo in due modi:
-
Validazione al confine. Sul backend, ogni risposta dell'API è tipizzata esplicitamente. Se aggiungo un campo al modello, devo aggiungerlo anche al tipo della risposta — e Zod valida che i dati in ingresso corrispondano.
-
Test di integrazione leggeri. Non ho test unitari su tutto, ma ho alcuni test che chiamano le API reali e verificano la forma della risposta. Se cambia un campo, il test fallisce, e so che c'è qualcosa da aggiornare sul frontend.
Quando avrei scelto diversamente
Se ReD Sposi fosse un prodotto long-term con una roadmap, avrei investito nel package condiviso dall'inizio. Il costo di setup si ammortizza su un orizzonte lungo — ogni modifica al modello dei dati che non richiede aggiornamenti manuali in tre posti diversi è tempo risparmiato.
Per un progetto che finisce ad agosto, il calcolo era diverso. Il costo del setup avanzato era certo; il beneficio dipendeva da quante volte avrei cambiato i modelli in modo significativo. Con una scadenza ravvicinata e una feature list abbastanza stabile, la complessità aggiuntiva non si ripagava.
La lezione
Non esiste un'architettura giusta in assoluto — esiste un'architettura giusta per il contesto in cui si lavora.
Un package condiviso è la risposta corretta per un prodotto long-term con un team. Il copy-paste consapevole è la risposta corretta per un progetto con scadenza fissa costruito da solo. Zod come source of truth è la risposta corretta se la validazione runtime è un requisito di prodotto.
La scelta sbagliata è quella che risolve il problema teorico invece del problema reale.