Come creare un'app per calendario - Guida completa

Autori
Pubblicato il

Se sei uno sviluppatore incaricato di integrare un calendario nella tua applicazione, oppure un imprenditore che sta pensando di creare un’app di calendario o un’app che integri funzioni di calendario, questo articolo fa per te.

Impareremo come costruire un’app di calendario, affrontando ogni decisione passo dopo passo, scegliendo le migliori tecnologie e imparando le sfide e le migliori pratiche.

Se desideri vedere l’implementazione completa e il codice sorgente, visita la nostra app di esempio Unified Calendar View su GitHub. Tutte le istruzioni su come eseguire l’applicazione in locale si trovano nel file README.

Qual è l’obiettivo di questo articolo?

Come menzionato nell’introduzione, lo scopo di questo articolo è aiutarti a costruire un’app di calendario completa o semplicemente integrare i provider di calendario nella tua applicazione esistente, dove gli utenti possono collegare i propri calendari e gestirli tramite la tua app.

Questo articolo è utile anche se desideri integrarti con i provider di calendario senza fornire un’interfaccia di calendario. Esempi potrebbero essere un’app di attività, un’app di incontri o persino una funzione che inserisce un evento nei calendari di un utente.

Puoi liberamente prendere parti di questo articolo e usarle nel tuo codice, incluse le scelte tecnologiche, le API unificate o parti del codice stesso.

Ti invitiamo a consultare la OneCal Unified Calendar API se desideri integrare tutti i provider di calendario utilizzando una singola API.

Panoramica dell’architettura e dello stack tecnologico

La piattaforma scelta per illustrare questo esempio è il web, poiché è più semplice da configurare e dispone di un ampio supporto della community per le librerie di calendario.

Il linguaggio di programmazione che utilizzeremo è TypeScript, e il framework web sarà Next.js.

Ecco una panoramica dello stack tecnologico che utilizzeremo:

  • Frontend: Next.js (app router, TypeScript)
  • Backend: Next.js API routes + tRPC
  • Database: PostgreSQL (utilizzando Prisma come ORM)
  • API di calendario: OneCal Unified Calendar API
  • Hosting: indipendente dal provider, puoi ospitarlo su Vercel o ovunque preferisci
  • Autenticazione: OAuth2 (Google, Microsoft) tramite better-auth

Di seguito è riportato il diagramma architetturale e come lo stack tecnologico si collega:

Calendar App Architecture Diagram

Definizioni:

  • Client (1): Il dispositivo dell’utente finale. In questo esempio viene mostrato un dispositivo mobile, ma potrebbe essere un desktop, un laptop, ecc. (qualsiasi dispositivo che supporti browser web come Google Chrome). Il client è responsabile del rendering del frontend Next.js, della gestione delle interazioni dell’utente e della comunicazione con il backend tramite richieste HTTPS sicure.

  • Web Server (2): Il Web Server ospita e serve l’interfaccia utente, un’applicazione web Next.js (App Router). Fornisce HTML, CSS e JavaScript ottimizzati al client e offre rendering lato server (SSR) e rigenerazione statica incrementale (ISR) per prestazioni elevate e vantaggi SEO.

  • Next.js API (3): L’API di Next.js funge da livello backend, implementato utilizzando le API routes e tRPC all’interno della stessa applicazione Next.js. Agisce come hub centrale che collega il frontend, il database e le integrazioni esterne come la OneCal Unified Calendar API. A differenza degli endpoint REST tradizionali, tRPC consente una comunicazione end-to-end sicura a livello di tipo tra frontend e backend, senza la necessità di definire uno schema API separato. Ciò significa che il client può chiamare direttamente le procedure backend, con inferenza completa dei tipi TypeScript. Questo aiuta a migliorare la velocità di sviluppo e a ridurre gli errori in fase di runtime.

  • PostgreSQL (4): Il database PostgreSQL memorizza tutti i dati persistenti dell’applicazione, inclusi utenti, sessioni, account di calendario collegati, ecc. È il sistema di registrazione per tutti i dati relativi agli utenti e agli stati di sincronizzazione. Con Prisma come livello ORM, lo schema è mappato in modo chiaro sul database, rendendo le migrazioni e le query facili da gestire.

  • OneCal Unified Calendar API (5): La OneCal Unified Calendar API è l’API che utilizziamo per integrare tutti i provider di calendario tramite un’API standardizzata. OneCal Unified semplifica l’integrazione con tutti i provider di calendario, evitando di dover creare implementazioni separate, gestire interfacce dati diverse, mantenere più integrazioni o affrontare modifiche alle API. La nostra API comunica con la OneCal Unified Calendar API inviando la chiave API e specificando i calendari su cui vogliamo eseguire operazioni (CRUD di eventi, calendari, e altro), indipendentemente dal provider. In risposta, OneCal comunica con i provider di calendario e restituisce la risposta in un formato standardizzato per tutti i provider.

Nota che il Web Server (2) e la Next.js API (3) possono essere ospitati sullo stesso server (utilizzando Vercel, Docker, ecc.), ma sono separati nel diagramma solo per comprendere visivamente che esistono un server UI e un server API, anche se entrambi appartengono alla stessa codebase Next.js e sono generalmente ospitati nello stesso server.

Progettazione del modello di dati

Dopo aver spiegato l’architettura e lo stack tecnologico, è il momento di definire il nostro modello di dati. Utilizzeremo Prisma come ORM di riferimento.

Questo schema Prisma definisce la struttura dei dati per un’applicazione di calendario di base che supporta l’autenticazione degli utenti, la connessione degli account di calendario (Google, Microsoft) e la sincronizzazione degli eventi tramite un’API unificata.

I modelli più importanti sono:

1. User

Rappresenta un utente finale dell’app. Ogni utente può avere più sessioni, account collegati (OAuth) e account di calendario. Campi come email, name e onboardingCompletedAt aiutano a tracciare il profilo e lo stato di completamento dell’onboarding.

2. CalendarAccount

Rappresenta un account di calendario esterno collegato (ad esempio, un account Google o Microsoft). Memorizza il provider, l’email e lo status (attivo o scaduto). Ogni CalendarAccount appartiene a un singolo User e può contenere più voci Calendar.

3. Calendar

Rappresenta un singolo calendario (come “Lavoro”, “Personale” o “Famiglia”) all’interno di un account collegato. Include campi di visualizzazione come name, color, timezone e flag come isPrimary o isReadOnly. Ogni calendario è collegato sia a un User che al CalendarAccount da cui proviene.

4. Account

Gestisce i dati del provider OAuth (Google o Microsoft). Memorizza i token di accesso e aggiornamento, i tempi di scadenza e le informazioni sugli ambiti (scope), utilizzati per l’autenticazione e la sincronizzazione del calendario.

5. Session

Tiene traccia delle sessioni di accesso attive degli utenti. Contiene campi come token, expiresAt, ipAddress e userAgent per gestire e proteggere le sessioni attive.

6. Verification

Utilizzato per verifiche una tantum, come link magici per il login via email o codici di autenticazione senza password. Memorizza identificatori temporanei e tempi di scadenza.

7. Enums

  • CalendarAccountProvider: definisce i provider supportati (GOOGLE, MICROSOFT)
  • CalendarAccountStatus: traccia se un account collegato è ACTIVE o EXPIRED

Diagramma ER del database:

Database ER Diagram

Apri il file schema.prisma nel nostro repository GitHub per visualizzare lo schema completo del database, inclusi tipi e relazioni.

Costruire il Backend

Come accennato, utilizzeremo le API routes di Next.js per costruire l’API, poiché è molto comodo avere API e UI nello stesso codice e ospitati sullo stesso server. Questo significa che puoi eseguire UI e API contemporaneamente.

Abbiamo scelto di usare le API routes di Next.js perché ha senso dal punto di vista della complessità, dato che stiamo costruendo un’app di calendario semplice, dove gli utenti effettuano il login, collegano i propri calendari e possono creare, aggiornare, o eliminare eventi. Se stai costruendo qualcosa di più complesso, puoi usare Node.js, Nest.js o qualsiasi altro framework. Le entità del database rimarranno le stesse, e la OneCal Unified Calendar API che utilizziamo per interagire con tutti i provider di calendario è basata su HTTP, quindi può essere chiamata da qualsiasi linguaggio o framework.

Nota che non stiamo usando le API routes di Next.js “pure”, ma utilizziamo tRPC per rendere le nostre API completamente tipizzate end-to-end.

Creazione dell’autenticazione

Utilizzeremo better-auth come framework di autenticazione. Better Auth semplifica l’autenticazione in modo notevole. Segui la guida ufficiale su come integrare Better Auth con Next.js, poiché i passaggi sono quasi identici. Puoi anche consultare il file auth nel nostro repository per approfondire.

Configurare la OneCal Unified Calendar API per comunicare con tutti i provider di calendario

Il principale punto critico nella creazione di un’app di calendario, o nell’integrazione dei calendari in un’applicazione esistente, è la gestione delle API specifiche dei vari provider. Questo comporta un costo di tempo, poiché occorre imparare ogni API separatamente, gestire strutture dati differenti, richieste, risposte e altro. Inoltre, dovremmo creare un’integrazione separata per ogni provider e mantenerle dopo lo sviluppo.

Una soluzione eccellente a questo problema è utilizzare una Calendar API unificata che ci consente di integrarci con tutti i provider di calendario tramite un’unica API standardizzata. In questo esempio, utilizzeremo la OneCal Unified Calendar API.

Per iniziare con OneCal Unified, segui questi passaggi:

  1. Il primo passo è registrarti su OneCal Unified e creare un account gratuito.

    OneCal Unified Signup Page
  2. Dopo la registrazione, abilita i provider di calendario che desideri integrare. Ti consigliamo di abilitare Google Calendar e Outlook per comprendere appieno la potenza di un prodotto basato su un’API di calendario unificata. Nota che non devi creare un client Google o Microsoft, poiché per il sandbox e lo sviluppo puoi utilizzare il client Google di OneCal Unified, che ti consente di collegare account Outlook o Google Calendar alla tua applicazione.

    Enable Calendar Provider
  3. Crea una chiave API e salvala nella variabile di ambiente ONECAL_UNIFIED_API_KEY

    Create an API Key Illustration

Creare il client della OneCal Unified API

Dopo aver configurato la OneCal Unified Calendar API e ottenuto la chiave API, è il momento di creare il client che interagisce con la OneCal Unified Calendar API:

import { env } from "@/env";
import type {
  EndUserAccount,
  PaginatedResponse,
  UnifiedCalendar,
  UnifiedEvent as UniversalEvent,
} from "@/server/lib/onecal-unified/types";
import ky from "ky";

export const onecalUnifiedApi = ky.create({
  prefixUrl: env.NEXT_PUBLIC_ONECAL_UNIFIED_URL,
  headers: {
    "x-api-key": env.ONECAL_UNIFIED_API_KEY,
  },
});

export async function getEndUserAccountById(id: string) {
  const response = await onecalUnifiedApi.get<EndUserAccount>(
    `endUserAccounts/${id}`,
  );
  return response.json();
}

export async function getCalendarsForEndUserAccount(endUserAccountId: string) {
  const response = await onecalUnifiedApi.get<
    PaginatedResponse<UnifiedCalendar>
  >(`calendars/${endUserAccountId}`);
  return response.json();
}

interface GetCalendarEventsParams {
  pageToken?: string;
  pageSize?: number;
  syncToken?: string;
  startDateTime?: string;
  endDateTime?: string;
  timeZone?: string;
  expandRecurrences?: boolean;
}

export async function getCalendarEvents(
  endUserAccountId: string,
  calendarId: string,
  params: GetCalendarEventsParams = {},
) {
  const queryParams = new URLSearchParams(params as Record<string, string>);

  const response = await onecalUnifiedApi.get<
    PaginatedResponse<UniversalEvent>
  >(`events/${endUserAccountId}/${calendarId}?${queryParams}`);
  return response.json();
}

export async function getCalendarEvent(
  endUserAccountId: string,
  calendarId: string,
  eventId: string,
) {
  const response = await onecalUnifiedApi.get<UniversalEvent>(
    `events/${endUserAccountId}/${calendarId}/${eventId}`,
  );
  return response.json();
}

export async function createCalendarEvent(
  endUserAccountId: string,
  calendarId: string,
  event: Partial<UniversalEvent>,
) {
  const response = await onecalUnifiedApi.post<UniversalEvent>(
    `events/${endUserAccountId}/${calendarId}`,
    {json: event},
  );
  return response.json();
}

export async function editCalendarEvent(
  endUserAccountId: string,
  calendarId: string,
  eventId: string,
  event: Partial<UniversalEvent>,
) {
  const response = await onecalUnifiedApi.put<UniversalEvent>(
    `events/${endUserAccountId}/${calendarId}/${eventId}`,
    {json: event},
  );
  return response.json();
}

export async function deleteCalendarEvent(
  endUserAccountId: string,
  calendarId: string,
  eventId: string,
) {
  await onecalUnifiedApi.delete(
    `events/${endUserAccountId}/${calendarId}/${eventId}`,
  );
}

Puoi trovare i tipi del client e le altre classi rilevanti in questa posizione su GitHub.

Il diagramma di sequenza spiega come l’app di esempio del calendario interagisce con la OneCal Unified Calendar API per offrire un’integrazione fluida con tutti i provider di calendario.

API communication sequence diagram

Creare le API routes

Dopo aver configurato il client della OneCal Unified Calendar API, siamo pronti per creare le rotte API per la gestione degli account di calendario e degli eventi. Nota che non è necessario creare manualmente API per le sessioni, poiché ci affidiamo a BetterAuth per questo scopo.

L’API contiene definizioni di rotte per:

  • Account di calendario: Rotta che espone metodi HTTP per elencare tutti gli account di calendario e per eliminarne uno tramite ID.
  • Eventi del calendario: Rotta che espone metodi HTTP per eseguire operazioni CRUD sugli eventi.
  • Calendari: Rotta che espone metodi HTTP per aggiornare i calendari.

Una definizione di rotta utilizzando tRPC sarebbe simile a questa:

export const calendarEventsRouter = createTRPCRouter({
  getCalendarEvent: publicProcedure
    .input(
      z.object({
        endUserAccountId: z.string(),
        calendarId: z.string(),
        eventId: z.string(),
      }),
    )
    .query(async ({ ctx, input }) => {
      return await getCalendarEvent(
        input.endUserAccountId,
        input.calendarId,
        input.eventId,
      );
    }),
});

Il metodo getCalendarEvent proviene dal client della OneCal Unified Calendar API che abbiamo costruito sopra.

Apri il percorso delle rotte API nel nostro repository GitHub per visualizzare il contenuto completo di ogni rotta API, poiché incollarle tutte qui renderebbe l’articolo ripetitivo.

Costruire il Frontend

Il frontend verrà costruito utilizzando Next.js + TypeScript. Quando si sviluppa un’app di calendario, il componente più importante è… esatto, il calendario.

In base alla nostra esperienza, le migliori librerie per interfacce calendario in Next.js e React sono:

Per questo esempio, abbiamo scelto di utilizzare react-big-calendar per la sua semplicità d’uso con Next.js, ma tieni presente che consigliamo fullcalendar per le applicazioni in produzione, poiché è più personalizzabile e dispone di un supporto comunitario più ampio.

Inoltre, fullcalendar è disponibile anche per altri framework come Svelte, Vue.js, ecc.

Utilizzo di react-big-calendar

      <Calendar
        culture="en-US"
        localizer={localizer}
        events={events}
        defaultView="week"
        eventPropGetter={eventPropGetter}
        components={components}
        onSelectSlot={(slotInfo) => {
          setCreateEventStart(slotInfo.start);
          setCreateEventEnd(slotInfo.end);
          setCreateEventOpen(true);
        }}
        onSelectEvent={(event) => {
          setSelectedEvent(event);
        }}
        selectable
        onRangeChange={(range) => {
          // Week view: range is array of dates
          if (Array.isArray(range) && range.length >= 2) {
            setDateRange([range[0]!, range[range.length - 1]!]);
            return;
          }
          // Month view: range is object with start/end
          if (
            range &&
            typeof range === "object" &&
            "start" in range &&
            "end" in range
          ) {
            setDateRange([range.start, range.end]);
            return;
          }
          // Day view: range is a single Date
          if (range instanceof Date) {
            setDateRange([range, range]);
            return;
          }
        }}
      />

Per visualizzare l’implementazione completa, apri il percorso src/app/(protected)/(calendar) nel repository GitHub.
Il componente principale è la pagina events-calendar.tsx.
Puoi anche trovare componenti per la modifica, l’eliminazione e la creazione di eventi (inclusi gli eventi ricorrenti).

Ecco come appare il calendario:

Calendar UI

L’utente può cliccare su una cella e creare un evento:

Calendar App - Create event UI

Quando l’utente clicca su un evento esistente, ha la possibilità di eliminarlo o modificarlo.

Calendar App - Edit or Delete Event UI

Quando l’evento è ricorrente, l’utente può scegliere se modificare solo l’istanza selezionata o l’intera serie.

Calendar App - Edit Recurring Event Popup

Ecco come appare l’interfaccia di modifica evento:

Calendar App- Edit Event UI

L’interfaccia necessita ancora di un po’ di rifinitura, ma non abbiamo voluto creare un’app di calendario “perfetta”, poiché l’obiettivo è mostrare come costruire un’app funzionale e un’integrazione di calendario.
Puoi occuparti dello stile grafico e assicurarti che rispecchi il brand della tua applicazione.

Sfide Comuni e Migliori Pratiche

Creare un’app di calendario o aggiungere funzionalità di calendario non è sempre semplice. Anche se la funzionalità principale può sembrare banale, ci sono molti piccoli dettagli che possono causare problemi in seguito.
Di seguito trovi alcune delle sfide più comuni e le migliori pratiche per affrontarle.

1. Fusi orari

Sfida:

Gli eventi possono essere visualizzati all’orario sbagliato quando gli utenti si trovano in fusi orari diversi.

Migliore pratica:

  • Salva sempre gli orari nel database in formato UTC. Questo esclude gli eventi del calendario, poiché non si consiglia di archiviare gli eventi direttamente nel database. Quando recuperi gli eventi tramite l’API del provider di calendario, dovresti comunque ricevere anche il fuso orario dell’evento.
  • Converti l’orario nel fuso orario locale dell’utente solo al momento della visualizzazione nel frontend.
  • Utilizza una libreria come date-fns-tz o luxon per gestire le conversioni di fuso orario. In questo esempio, usiamo date-fns e date-fns-tz.

2. Eventi ricorrenti

Sfida:

Gestire eventi che si ripetono (giornalieri, settimanali, mensili) può essere complesso, soprattutto quando gli utenti vogliono modificare o eliminare solo un’istanza specifica.

Migliore pratica:

  • Permetti all’utente di scegliere se aggiornare un solo evento o l’intera serie.
    Questa pratica è adottata da Google Calendar, Outlook e molti altri client di calendario. L’abbiamo seguita anche nella nostra app di calendario.

3. Scadenza dei token OAuth

Sfida:

Gli utenti possono perdere la connessione ai propri calendari se i token scadono o vengono revocati.

Migliore pratica:

  • Memorizza i refresh token in modo sicuro, per poter ottenere automaticamente nuovi access token.
  • Gestisci gli errori dei token in modo elegante e invita l’utente a ricollegare il proprio account, se necessario.

4. Mantenere i dati sincronizzati

Sfida:

I dati del calendario possono diventare obsoleti se vengono recuperati una sola volta.

Migliore pratica:

  • Utilizza i webhook della OneCal Unified Calendar API per ricevere aggiornamenti quando gli eventi cambiano.
  • Ti consigliamo di recuperare gli eventi dai provider di calendario quando l’utente interagisce con l’app.
    Evita di memorizzare gli eventi nel tuo database: mantenerli sincronizzati con tutti i provider di calendario è difficile e spesso non necessario, poiché la OneCal Unified Calendar API consente di recuperarli direttamente in tempo reale.

5. Gestione degli errori API

Sfida:

Le API esterne (Google, Outlook, iCloud) possono restituire errori, limiti di velocità o guasti temporanei.

Migliore pratica:

  • Aggiungi logica di retry per errori temporanei (i timeout sono una buona opzione).
  • Rispetta i limiti di velocità delle API e riduci il numero di richieste quando necessario.
    Tieni presente che anche la OneCal Unified Calendar API e i provider di calendario come Google Calendar e Outlook Calendar applicano limiti di rate limit.
  • Registra tutte le richieste fallite per semplificare il debug.

6. Grandi volumi di eventi

Sfida:

Alcuni utenti possono avere centinaia o migliaia di eventi, rallentando l’app.

Migliore pratica:

  • Carica gli eventi in modo paginato. La paginazione è supportata da tutti i principali provider di calendario.
    Se utilizzi la OneCal Unified Calendar API, noterai che tutti i risultati sono già paginati, quindi non avrai questo problema.
  • Recupera solo gli eventi per l’intervallo di date visibile (ad esempio, la settimana o il mese corrente).
    È buona pratica ottenere solo i dati necessari — per esempio, mostra gli eventi del giorno, della settimana o del mese, in base alla vista selezionata.

7. Privacy e sicurezza degli utenti

Sfida:

I dati del calendario contengono spesso informazioni private e sensibili.

Migliore pratica:

  • Non archiviare gli eventi del calendario nel tuo database. È sufficiente conservare la chiave di accesso e quella di aggiornamento.
  • Cifra i token e i campi sensibili nel tuo database. Ti consigliamo di criptare il database a riposo; ad esempio, AWS RDS offre la crittografia dei dati integrata.
  • Consenti agli utenti di scollegare i propri account calendario in qualsiasi momento.
    È molto importante: se non offri la possibilità di disconnettere o eliminare i calendari, gli utenti revocheranno l’accesso direttamente dalle impostazioni dell’account Google o Microsoft.

FAQ

1. Posso utilizzare un backend diverso invece delle API routes di Next.js?

Sì. Anche se in questo esempio utilizziamo le API routes di Next.js con tRPC, puoi usare qualsiasi framework backend come Nest.js, Express o Django.
La parte fondamentale è che il tuo backend comunichi con la OneCal Unified Calendar API tramite richieste HTTPS.
La struttura del database e la logica dell’API rimarranno sostanzialmente le stesse.

2. Devo creare le mie app sviluppatore Google o Microsoft?

No, puoi utilizzare i client Google e Microsoft di OneCal Unified durante lo sviluppo.
Quando la tua app sarà online, potrai creare le tue credenziali OAuth2 se desideri avere pieno controllo e branding personalizzato per i tuoi utenti.

3. Posso utilizzare un database diverso da PostgreSQL?

Sì. Prisma supporta molti database come MySQL, SQLite e MongoDB.
Abbiamo scelto PostgreSQL perché è affidabile, scalabile e facile da configurare in produzione.
Puoi comunque scegliere qualsiasi altro database e ORM, a seconda del tuo stack tecnologico.

4. La OneCal Unified Calendar API è gratuita?

Puoi iniziare gratuitamente creando un account su OneCal Unified.
È disponibile un piano gratuito ideale per test e piccoli progetti.
Per l’uso in produzione, puoi effettuare l’upgrade in base al tuo utilizzo e al numero di account collegati.

5. Cosa succede se un utente scollega il proprio calendario?

Quando un utente scollega un calendario, l’app deve rimuovere il relativo CalendarAccount e i Calendars associati dal database.
Puoi mantenere dati locali a fini analitici se necessario, ma assicurati di non sincronizzare o accedere più a calendari disconnessi.

6. Posso aggiungere notifiche o promemoria?

Sì. Puoi costruire i promemoria direttamente nella tua app oppure utilizzare il sistema di notifiche nativo del calendario connesso (Google, Outlook, ecc.).

7. Cosa succede se l’API limita le mie richieste?

La OneCal Unified API include limiti di velocità integrati per garantire la stabilità del sistema.
Se raggiungi il limite, attendi e riprova dopo un breve intervallo.
Tieni presente che anche i provider di calendario hanno propri limiti di rate limit a livello di applicazione.
Sia Google che Microsoft offrono la possibilità di richiedere limiti più elevati se necessario.

8. È possibile sincronizzare gli eventi in entrambe le direzioni?

Sì. La OneCal Unified API supporta la sincronizzazione bidirezionale, il che significa che puoi sia leggere che scrivere eventi nei calendari collegati.
Riceverai notifiche tramite webhook quando si verificano modifiche lato provider.

9. Come posso distribuire questo progetto?

Puoi distribuire facilmente l’app su Vercel.
Assicurati di impostare le variabili d’ambiente (DATABASE_URL, ONECAL_UNIFIED_API_KEY, credenziali OAuth, ecc.) nelle impostazioni del progetto.
Puoi anche containerizzarla usando Docker se preferisci un maggiore controllo.