Como criar um aplicativo de calendário – Um guia completo
Índice
Integre vários calendários usando uma única API
Use a API de Calendário Unificada do OneCal para integrar rapidamente vários provedores de calendário ao seu aplicativo e não se preocupar com manutenção ou testes.
ou um empreendedor que está pensando em criar um aplicativo de calendário, ou um app que tenha integrações com calendários, este artigo é para você.
Vamos aprender como construir um aplicativo de calendário, enfrentar cada decisão passo a passo, escolher as melhores tecnologias e entender os desafios e melhores práticas.
Se você quiser ver a implementação completa e o código-fonte, acesse nosso exemplo de aplicativo com Visualização Unificada de Calendário no GitHub. Todas as instruções para executar a aplicação localmente estão disponíveis no arquivo README.
Qual é o escopo deste artigo?
Como mencionado na introdução, o objetivo deste artigo é ajudá-lo a construir um aplicativo de calendário completo, ou simplesmente integrar provedores de calendário ao seu aplicativo existente, onde os usuários possam conectar seus calendários e gerenciá-los através do seu app.
Este artigo também é útil mesmo que você deseje integrar provedores de calendário sem oferecer uma interface de calendário. Exemplos seriam um app de tarefas, um app de namoro ou até mesmo um recurso que insere um evento nos calendários dos usuários.
Sinta-se à vontade para aproveitar certas partes do artigo e usá-las em seu código, incluindo escolhas de tecnologia, APIs unificadas ou trechos do código.
Acesse a OneCal Unified Calendar API se você estiver procurando por uma forma de integrar todos os provedores de calendário usando uma única API.
Visão geral da arquitetura e da stack de tecnologia
A plataforma que escolhemos para ilustrar este exemplo é a web, pois é mais fácil de configurar e há bastante suporte da comunidade para bibliotecas de calendário.
A linguagem de programação que usaremos é TypeScript e o framework web será Next.js.
Aqui está uma visão geral da stack de tecnologia que utilizaremos:
- Frontend: Next.js (app router, TypeScript)
- Backend: Rotas de API do Next.js + tRPC.
- Banco de Dados: PostgreSQL (usando
Prismacomo ORM) - APIs de Calendário: OneCal Unified Calendar API
- Hospedagem: Agnóstica de provedor, você pode hospedar na Vercel ou onde preferir.
- Autenticação: OAuth2 (Google, Microsoft) via
better-auth
Abaixo está o diagrama arquitetural e como a stack se conecta:

Definições:
- Cliente (1): O dispositivo do usuário final. Esta ilustração usa um dispositivo móvel, mas pode ser um desktop, laptop, etc. (qualquer dispositivo que suporte navegadores web como o Google Chrome). O cliente é responsável por renderizar o frontend do Next.js, lidar com interações do usuário e se comunicar com o backend por meio de requisições HTTPS seguras.
- Servidor Web (2): O servidor web hospeda e serve a interface do usuário, uma aplicação web Next.js (App Router). Ele entrega HTML, CSS e JavaScript otimizados para o cliente e fornece renderização do lado do servidor (SSR) e regeneração estática incremental (ISR) para melhor desempenho e benefícios de SEO.
- API Nextjs (3): A API do Next.js funciona como a camada de backend, implementada usando rotas de API e tRPC dentro da mesma aplicação Next.js. Ela serve como hub central conectando o frontend, o banco de dados e integrações externas como a OneCal Unified Calendar API. Diferente de endpoints REST tradicionais, o tRPC permite comunicação com tipagem segura de ponta a ponta entre frontend e backend sem precisar definir um schema de API separado. Isso permite que o cliente chame procedimentos do backend diretamente, com inferência completa de tipos em TypeScript. Isso ajuda a melhorar a velocidade de desenvolvimento e reduzir erros em tempo de execução.
- PostgreSQL (4): O banco de dados PostgreSQL armazena todos os dados persistentes da aplicação, incluindo usuários, sessões, contas de calendário conectadas, etc. Ele é o sistema de registro para todos os dados relacionados ao usuário e estados de sincronização. Com o Prisma como camada ORM, o schema é mapeado claramente para o banco de dados, tornando migrações e queries fáceis de gerenciar.
- OneCal Unified Calendar API (5): A OneCal Unified Calendar API é a API que usamos para integrar com todos os provedores de calendário usando uma API padronizada. A OneCal Unified facilita nossa integração com todos os provedores de calendário, sem a necessidade de implementar códigos separados, lidar com diferentes formatos de dados, manter múltiplas integrações ou se preocupar com mudanças nas APIs. Nossa API se comunica com a OneCal Unified Calendar API enviando a chave de API e quais calendários queremos operar (CRUD de eventos, calendários, etc.), independentemente do provedor. Em resposta, a OneCal se comunica com os provedores de calendário e retorna a resposta em um formato padronizado para todos os provedores.
Observe que o Servidor Web (2) e a API Next.js (3) podem ser hospedados no mesmo servidor (usando Vercel, Docker, etc.), mas estão separados apenas para ilustrar visualmente que existe um servidor de UI e um servidor de API, mesmo que ambos estejam na mesma base de código Next.js e geralmente sejam hospedados juntos.
Definindo o modelo de dados
Após explicar a arquitetura e a stack de tecnologia, é hora de definir nosso modelo de dados. Usaremos o Prisma como nosso ORM preferido.
Este schema do Prisma define a estrutura de dados para um aplicativo de calendário básico que oferece suporte à autenticação de usuários, conexões com contas de calendário (Google, Microsoft) e sincronização de eventos por meio de uma API unificada.
Os modelos mais importantes são:
1. User
Representa um usuário final do app. Cada usuário pode ter múltiplas sessões, contas conectadas (OAuth) e contas de calendário. Campos como email, name e onboardingCompletedAt ajudam a acompanhar o perfil e o status de onboarding.
2. CalendarAccount
Representa uma conta externa de calendário vinculada (por exemplo, uma conta do Google ou Microsoft). Armazena o provider, email e status (ativo ou expirado). Cada CalendarAccount pertence a um User e pode conter múltiplas entradas de Calendar.
3. Calendar
Representa um calendário individual (como “Trabalho”, “Pessoal” ou “Família”) dentro de uma conta vinculada. Inclui campos de exibição como name, color, timezone, e flags como isPrimary ou isReadOnly. Cada calendário está vinculado tanto a um User quanto à CalendarAccount de origem.
4. Account
Lida com os dados do provedor OAuth (Google ou Microsoft). Armazena tokens de acesso e atualização, tempos de expiração e informações de escopo, usados para autenticação e sincronização de calendários.
5. Session
Acompanha sessões de login ativas dos usuários. Contém campos como token, expiresAt, ipAddress e userAgent para gerenciar e proteger sessões ativas.
6. Verification
Usado para verificações únicas, como links mágicos de login por e-mail ou códigos de autenticação sem senha. Armazena identificadores temporários e tempos de expiração.
7. Enums
CalendarAccountProvider: Define os provedores suportados (GOOGLE,MICROSOFT).CalendarAccountStatus: Acompanha se uma conta conectada estáACTIVEouEXPIRED.
O diagrama ER do banco de dados:

Abra o arquivo schema.prisma em nosso repositório GitHub para ver o schema completo do banco de dados, incluindo os tipos e conexões.
Construindo o Backend
Como mencionado anteriormente, usaremos as rotas de API do Next.js para construir a API, pois é muito prático ter a API e a UI na mesma base de código e hospedadas no mesmo servidor. Isso significa que você pode executar a UI e a API simultaneamente.
Optamos por usar as rotas de API do Next.js porque isso faz sentido do ponto de vista da complexidade, já que estamos construindo um aplicativo de calendário simples, no qual os usuários fazem login, conectam seus calendários e realizam operações como criar eventos, atualizar eventos, excluir eventos, etc. Se você estiver construindo algo mais complexo, fique à vontade para usar Node.js, Nest.js ou qualquer outro framework. As entidades do banco de dados devem ser as mesmas, e a OneCal Unified Calendar API que estamos usando para interagir com todos os provedores de calendário é baseada em HTTP, portanto você pode chamá-la a partir de qualquer linguagem de programação ou framework que utilizar.
Observe que não estamos usando as rotas de API do Next.js como estão; estamos usando o tRPC para tornar nossas APIs seguras em termos de tipos de ponta a ponta.
Construindo a autenticação
Usaremos o better-auth como nosso framework de autenticação. O Better Auth torna a autenticação muito fácil e sem complicações. Siga o guia do Better Auth sobre como integrar o Better Auth com Next.js, pois os passos são quase idênticos aos do guia. Sinta-se à vontade para explorar o arquivo de autenticação em nosso repositório para saber mais.
Configurando a OneCal Unified Calendar API para se comunicar com todos os provedores de calendário
O principal ponto problemático ao construir um app de calendário ou integrar calendários em um app ou recurso existente é lidar com APIs específicas de cada provedor. Isso tem um custo de tempo associado, pois precisaríamos aprender cada API separadamente, lidar com diferentes estruturas de dados, requisições, respostas, etc. Além disso, precisaríamos construir uma integração separada para cada provedor e manter as integrações após o desenvolvimento.
Uma ótima solução para esse problema é usar uma Calendar API unificada que nos ajude a integrar com todos os provedores de calendário usando uma API padronizada. Neste exemplo, usaremos a OneCal Unified Calendar API.
Para começar com a OneCal Unified, siga os passos abaixo:
- O primeiro passo é criar uma conta gratuita na OneCal Unified.

- Após se inscrever, ative os provedores de calendário que deseja integrar. Recomendamos ativar o Google Calendar e o Outlook, para que você entenda o poder de usar um produto de API de calendário unificada. Observe que você não precisa criar um cliente do Google ou Microsoft, pois para testes e desenvolvimento você pode usar o cliente Google da OneCal Unified, permitindo conectar contas do Outlook ou Google Calendar ao seu aplicativo.

- Crie uma chave de API e armazene-a na variável de ambiente
ONECAL_UNIFIED_API_KEY
Construindo o Cliente da API da OneCal Unified
Depois de configurar a OneCal Unified Calendar API e obter a chave de API, é hora de construir o cliente da API que interage com a 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}`,
);
}
Você pode encontrar os tipos do cliente e outras classes relevantes neste local do GitHub.
O diagrama de sequência explica como o App de Calendário de Exemplo interage com a OneCal Unified Calendar API para fornecer uma integração perfeita com todos os provedores de calendário.

Criando as rotas da API
Depois de configurar o cliente da OneCal Unified Calendar API, estamos prontos para criar as rotas de API para gerenciar contas de calendário e eventos de calendário. Observe que não precisamos criar explicitamente APIs de sessão, pois estamos utilizando o BetterAuth para isso.
A API contém definições de rota para:
- Contas de calendário: Rota que expõe métodos HTTP para listar todas as contas de calendário e excluir uma conta de calendário por ID.
- Eventos de Calendário: Rota que expõe métodos HTTP para realizar operações CRUD em eventos de calendário.
- Calendários: Rota que expõe métodos HTTP para atualizar calendários.
Uma definição de rota usando tRPC seria algo como:
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,
);
}),
});O método getCalendarEvent vem do cliente da OneCal Unified Calendar API que construímos acima.
Abra o caminho das rotas de API no nosso repositório GitHub para ver o conteúdo de cada rota, pois colá-las neste artigo seria bastante repetitivo.
Construindo o Frontend
O frontend será construído usando Next.js + TypeScript. Ao criar um aplicativo de calendário, o componente mais importante é… isso mesmo, o calendário.
Com base em nossa experiência, as melhores bibliotecas de interface de calendário para Next.js e React são:
Para este exemplo, escolhemos usar react-big-calendar devido à facilidade de uso com Next.js, mas tenha em mente que recomendamos usar fullcalendar em aplicações de produção, pois ele é mais customizável e tem um suporte comunitário mais amplo.
Além disso, o fullcalendar está disponível em outras bibliotecas como Svelte, Vue.js, etc.
react-big-calendar usage:
<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;
}
}}
/>
Para visualizar a implementação completa, abra o caminho src/app/(protected)/(calendar) no repositório do GitHub. O componente principal é a página events-calendar.tsx. Você também pode encontrar componentes para editar eventos (incluindo eventos recorrentes), excluir eventos e criar eventos.
Veja como o Calendário aparece:

O usuário pode clicar em uma célula e criar um evento:

Quando o usuário clica em um evento existente, ele tem a opção de excluí-lo ou editá-lo.

Quando o evento é recorrente, o usuário tem a opção de editar apenas a instância selecionada ou toda a série.

Veja como é a interface de edição de evento:

A interface precisa de melhorias, mas não queríamos construir um aplicativo de calendário perfeito, já que o objetivo aqui é criar um app funcional e implementar integrações de calendário. Você pode cuidar dos estilos e garantir que combinem com a identidade visual da sua marca.
Desafios Comuns e Melhores Práticas
Construir um aplicativo de calendário ou adicionar recursos de calendário nem sempre é fácil. Mesmo que a funcionalidade principal pareça simples, existem muitos pequenos detalhes que podem causar problemas posteriormente. Abaixo estão alguns desafios comuns que você pode enfrentar e algumas melhores práticas para lidar com eles.
1. Fusos Horários
Desafio:
Eventos podem aparecer no horário errado quando usuários estão em fusos horários diferentes.
Melhor prática:
- Sempre salve horários em UTC no banco de dados. Isso exclui eventos de calendário, já que não recomendamos armazenar eventos de calendário no banco. Quando você busca eventos via API do provedor de calendário, você também deve receber o fuso horário do evento.
- Converta para o horário local do usuário apenas no frontend.
- Use uma biblioteca como date-fns-tz ou luxon para lidar com conversões de horário. Neste exemplo, usamos
date-fnsedate-fns-tz.
2. Eventos Recorrentes
Desafio:
Lidar com eventos que se repetem (diariamente, semanalmente, mensalmente) pode ser complexo, especialmente quando os usuários querem editar ou excluir apenas uma instância.
Melhor prática:
- Permitir que o usuário escolha entre atualizar apenas um evento ou toda a série. Essa prática é usada pelo Google Calendar, Outlook e muitos outros clientes. Nós também seguimos essa prática em nosso app de Calendário.
3. Expiração de Tokens OAuth
Desafio:
Os usuários podem perder a conexão com seus calendários se os tokens expirarem ou forem revogados.
Melhor prática:
- Armazene refresh tokens com segurança para obter novos tokens automaticamente.
- Trate erros de token de forma amigável e avise os usuários quando precisarem reconectar suas contas.
4. Manter os Dados Sincronizados
Desafio:
Os dados do calendário podem ficar desatualizados se você buscá-los apenas uma vez.
Melhor prática:
- Use webhooks da OneCal Unified Calendar API para receber atualizações quando eventos forem modificados.
- Recomendamos buscar eventos diretamente dos provedores quando o usuário interagir com o app de calendário. Não armazene eventos em seu banco de dados. Manter eventos sincronizados com todos os provedores é extremamente complexo e pouco vantajoso, considerando que você pode buscá-los facilmente usando a OneCal Unified Calendar API.
5. Tratamento de Erros de API
Desafio:
APIs externas (Google, Outlook, iCloud) podem retornar erros, limites de uso ou falhas temporárias.
Melhor prática:
- Adicione lógica de retry para erros temporários (usando timeouts, por exemplo).
- Respeite limites de requisição e faça backoff quando necessário. A OneCal Unified Calendar API possui limites, assim como Google Calendar e Outlook Calendar.
- Registre todas as falhas para facilitar o debug.
6. Calendários Grandes
Desafio:
Alguns usuários possuem centenas ou milhares de eventos, o que pode deixar o app lento.
Melhor prática:
- Carregue eventos em páginas (use paginação). A paginação é suportada por todos os provedores importantes. Na OneCal Unified Calendar API, todos os resultados são paginados.
- Busque apenas eventos do intervalo visível (ex.: semana atual ou mês atual). Obtenha apenas o necessário para o período exibido (dia, semana, mês, ano).
7. Privacidade e Segurança do Usuário
Desafio:
Dados de calendário geralmente incluem informações privadas.
Melhor prática:
- Não armazene eventos de calendário em seu banco de dados. Armazenar as chaves de acesso e atualização é o suficiente.
- Criptografe tokens e campos sensíveis no banco. Recomendamos criptografar o banco em repouso. Serviços como o AWS RDS oferecem isso nativamente.
- Permita que usuários desconectem contas de calendário a qualquer momento. Isso é muito importante, pois se você não permitir, eles simplesmente revogarão o acesso diretamente na conta Google/Microsoft, o que pode causar inconsistências.
FAQ
1. Posso usar outro backend em vez das rotas de API do Next.js?
Sim. Embora este exemplo use rotas de API do Next.js com tRPC, você pode usar qualquer framework backend como Nest.js, Express ou Django.
O ponto principal é que seu backend deve se comunicar com a OneCal Unified Calendar API usando requisições HTTPS. A estrutura do banco e a lógica de API permanecerão praticamente iguais.
2. Preciso criar meus próprios apps de desenvolvedor Google ou Microsoft?
Não, você pode usar os clientes Google e Microsoft da OneCal Unified durante o desenvolvimento.
Quando seu app estiver pronto para produção, você pode criar suas próprias credenciais OAuth2, caso deseje controle total e personalização para seus usuários.
3. Posso usar outro banco de dados em vez de PostgreSQL?
Sim. O Prisma suporta muitos bancos, como MySQL, SQLite e MongoDB.
Escolhemos PostgreSQL porque ele é confiável, escalável e fácil de configurar para produção. Mas você pode escolher qualquer banco e ORM de acordo com sua stack.
4. A OneCal Unified Calendar API é gratuita?
Você pode começar gratuitamente criando uma conta na OneCal Unified.
Existe um plano gratuito ideal para testes e pequenos projetos. Para uso em produção, você pode fazer upgrade de acordo com sua utilização e número de contas conectadas.
5. O que acontece se um usuário desconectar seu calendário?
Quando um usuário desconecta um calendário, o app deve remover a CalendarAccount e os Calendars correspondentes do seu banco de dados.
Você pode manter dados locais para analytics, se necessário, mas não deve mais sincronizar nem acessar calendários desconectados.
7. Posso adicionar notificações ou lembretes?
Sim. Você pode criar lembretes no seu app ou usar o sistema nativo de notificações do calendário conectado (Google, Outlook, etc.).
8. O que fazer se a API limitar minhas requisições?
A OneCal Unified API possui limites de uso para garantir estabilidade. Se você atingir o limite, faça um backoff e tente novamente após um curto período. Os provedores também possuem suas próprias quotas, e tanto Google quanto Microsoft permitem solicitar limites maiores.
9. É possível sincronizar eventos em ambas as direções?
Sim. A OneCal Unified API suporta sincronização bidirecional, permitindo ler e escrever eventos nos calendários conectados.
Você também receberá webhooks quando ocorrerem mudanças no provedor.
10. Como posso implantar este projeto?
Você pode implantar o app facilmente na Vercel.
Certifique-se de configurar suas variáveis de ambiente (DATABASE_URL, ONECAL_UNIFIED_API_KEY, credenciais OAuth, etc.) nas configurações do projeto.
Se preferir maior controle, você também pode conteinerizar o app usando Docker.