Pular para o conteúdo
Voltar aos projetos

Construindo um Kanban com Next.js, GraphQL em Route Handlers e Otimismo Inteligente

Logo do projeto Construindo um Kanban com Next.js, GraphQL em Route Handlers e Otimismo Inteligente

Recentemente, mergulhei de cabeça em um desafio que muitos desenvolvedores full-stack já enfrentaram: como construir uma aplicação complexa, interativa e performática, minimizando a complexidade da infraestrutura. Minha resposta a esse desafio se materializou em uma Prova de Conceito (POC) de um board Kanban completo, utilizando o Next.js para o frontend e para servir GraphQL diretamente de um Route Handler, eliminando a necessidade de um backend separado.

A grande pergunta que me motivou era: é realmente viável servir GraphQL diretamente de um Route Handler do Next.js e, ao mesmo tempo, construir um board Kanban com funcionalidades robustas de drag-and-drop, sem "inventar" dados? A resposta, após a conclusão da POC, é um retumbante sim!

A Pilha Tecnológica por Trás do Projeto

Para materializar essa visão, escolhi uma combinação de tecnologias modernas e eficientes:

  • Next.js 16: O framework React para o desenvolvimento full-stack, aproveitando seus Route Handlers para a API.

  • React 19: A biblioteca de UI mais recente, garantindo componentes otimizados.

  • graphql-yoga: Um servidor GraphQL simples e performático, integrado em /api/graphql.

  • Apollo Client v4: Para gerenciar o estado do GraphQL no frontend, incluindo cache e otimismo.

  • Postgres + Prisma: O banco de dados relacional robusto e o ORM de nova geração para interagir com ele.

  • Tailwind v4 + shadcn: Para um design responsivo, moderno e altamente personalizável.

  • dnd-kit: A biblioteca de drag-and-drop para React, conhecida por sua flexibilidade e acessibilidade.

Funcionalidades Abrangentes da POC

A POC não foi apenas um "olá mundo". Ela cobriu um conjunto significativo de funcionalidades para simular um aplicativo Kanban real:

  • CRUD Completo: Criação, leitura, atualização e exclusão de colunas e cards.

  • Drag-and-Drop Robusto: Reordenação de cards dentro de uma coluna, entre colunas, e também reordenação de colunas inteiras, tudo com suporte a mouse e teclado.

  • Busca Inteligente: Campo de busca com debounce para otimizar requisições ao servidor, filtrando cards por título ou descrição.

  • Filtro por Label: Capacidade de filtrar cards por etiquetas (labels) associadas.

  • Tema Dark/Light: Implementação de um tema alternável para melhor experiência do usuário.

Os Desafios e as Soluções Mais Interessantes

Embora a integração das tecnologias tenha sido relativamente suave, os detalhes e as nuances de um sistema interativo como um Kanban trouxeram alguns problemas bastante interessantes para resolver:

1. Otimismo que Sabe se Mentir

Em aplicações com drag-and-drop, a experiência do usuário é drasticamente melhorada com uma UI otimista. Isso significa que, ao arrastar um card ou uma coluna, o frontend aplica o movimento instantaneamente, antes mesmo de o servidor responder. O desafio aqui é garantir que, se o servidor discordar do movimento (por exemplo, devido a uma validação ou um estado obsoleto), a UI se reconcilie corretamente.

Minha solução envolveu replicar a lógica de reordenação. As funções applyMove e applyColumnMove, que aplicam o movimento no estado local do frontend, rodam exatamente o mesmo algoritmo que os resolvers do GraphQL no backend. Após o movimento otimista, uma mutação é enviada ao servidor. Se a resposta do servidor for diferente do estado otimista (comparado por identidade do item), o cache do Apollo Client é limpo automaticamente para aquela query, forçando um refetch. Essa abordagem permite uma experiência fluida, mas com a segurança de que o servidor é sempre a autoridade final.

"A UI otimista é como uma mentira bem contada: ela melhora a experiência, mas precisa ter um plano para quando a verdade vier à tona."

2. Filtro Ativo Trava o Drag-and-Drop

Um problema comum ao combinar filtros com reordenação é a discrepância entre a lista visível (filtrada) no cliente e a lista completa no servidor. Se o usuário arrasta um item em uma lista filtrada, o índice de drop calculado no cliente se refere à posição na lista filtrada. No entanto, o servidor precisa inserir esse item na lista completa, o que pode levar a um posicionamento incorreto.

A solução encontrada foi pragmática: pausar a reordenação (drag-and-drop) enquanto um filtro está ativo. Embora possa parecer uma limitação, é uma maneira eficaz de evitar inconsistências complexas. Quando o filtro é desativado, a funcionalidade de drag-and-drop é restaurada. Isso garante que o índice de drop no cliente sempre corresponda ao índice na lista completa que o servidor manipula.

3. Validação em Duas Camadas

A segurança e a integridade dos dados são primordiais. Por isso, implementei uma estratégia de validação em duas camadas. Primeiro, o frontend (no dialog de criação/edição) valida os dados para fornecer feedback instantâneo e amigável ao usuário. Isso melhora a experiência, evitando que o usuário envie dados inválidos para o servidor.

No entanto, a validação crucial ocorre no servidor, dentro dos resolvers do GraphQL. Mesmo que o GraphiQL (a interface para testar queries GraphQL) esteja exposto na mesma rota da API, e alguém tente "burlar" a validação do cliente enviando dados maliciosos diretamente, o servidor sempre fará sua própria validação. O servidor é a autoridade máxima e garantirá que apenas dados válidos sejam persistidos no banco de dados.

4. Filtros nas Variáveis da Query

Para implementar a busca e o filtro por label, utilizei variáveis na query GraphQL. Cada vez que o usuário digita no campo de busca ou seleciona uma label, as variáveis da query são atualizadas, o que aciona uma nova requisição ao servidor. Sem otimização, isso poderia causar um "piscar" na tela, exibindo um skeleton loader a cada nova letra digitada, o que degrada a experiência do usuário.

Para mitigar isso, aproveitei o recurso previousData do Apollo Client. Ao usar fetchPolicy: 'cache-and-network' e notifyOnNetworkStatusChange: true, e combinando com previousData, pude exibir os dados da requisição anterior enquanto a nova requisição está sendo processada. Isso mantém a tela estável e evita o incômodo piscar do skeleton, proporcionando uma transição de dados mais suave.

Estratégia de Testes Abrangente

Uma POC, mesmo sendo um projeto de exploração, se beneficia enormemente de uma boa cobertura de testes. Adotei uma abordagem em camadas:

  • Vitest: Utilizado para testes unitários e de integração. Os testes de integração foram executados contra um banco de dados Postgres isolado (kanbanql_test), garantindo que o banco de desenvolvimento (kanbanql_dev) nunca fosse afetado.

  • Playwright: Implementei 8 cenários de testes End-to-End (E2E). Esses testes foram executados contra uma build de produção da aplicação, simulando a interação real do usuário e garantindo que todo o fluxo, do frontend ao banco de dados, funcionasse como esperado.

  • CI (Continuous Integration): Um pipeline de CI foi configurado para rodar automaticamente o lint, o TypeScript checker, a build de produção e todos os testes (unitários, integração e E2E) a cada commit, garantindo a qualidade e a estabilidade do código.

Conclusão: O Full-Stack Sem Backend Separado é Real!

Esta POC demonstrou que é perfeitamente possível construir uma aplicação full-stack complexa e interativa usando Next.js, servindo GraphQL diretamente de Route Handlers, sem a necessidade de um servidor de backend separado. Essa abordagem simplifica a arquitetura, reduz a sobrecarga de gerenciamento e oferece uma experiência de desenvolvimento coesa.

Os desafios encontrados, como a otimização da UI otimista e a gestão de filtros e drag-and-drop, são problemas comuns em aplicações web interativas e foram superados com soluções inteligentes e pragmáticas. A combinação de Next.js, GraphQL e um conjunto robusto de ferramentas modernas oferece um caminho poderoso para desenvolvedores que buscam construir aplicações eficientes e escaláveis com menos complexidade.

Se você tem interesse em explorar o código e ver como tudo isso funciona na prática, o repositório está disponível:

https://github.com/gustavomssell/POC-Kanban