Vue-Python

FastAPI indo Full Stack

A história de como o Vue viu a cobra e se apaixonou.

O FastAPI é um daqueles softwares que me deixa desconfiado porque a história do backend em Python é agradável demais. Type hints em handlers no estilo Flask/Express viram parsing e validação. Async com auto reloads funciona sem invocar um manual arcano de configuração de framework. Some a isso seus schemas OpenAPI e os auto reloads e você sai do git init para uma API respeitável antes de a máquina de café terminar sua sequência de boot.

Então você precisa de um frontend.

Historicamente, era aqui que a via expressa iluminada por neon terminava abruptamente em um muro de concreto.

O FastAPI é muito bom em APIs, mas ele não consegue realmente servir seu frontend na raiz do site, a menos que você quisesse que todas as suas requisições fossem roteadas para o StaticFiles. E suporte a SPA é algo inédito. Você podia servir arquivos, renderizar templates ou acoplar a maquinaria que quisesse, mas a última milha entre um frontend JavaScript moderno e a aplicação Python ficava em grande parte como um exercício para o leitor.

E esse leitor era cada vez mais eu.

Enquanto isso, na terra do frontend

Eu uso o Vue para essa tarefa. O Vue vive na mesma arena do React e do Svelte: interfaces baseadas em componentes, estado reativo, roteamento no lado do cliente e a maquinaria moderna usual para transformar uma pilha de arquivos-fonte em algo que um navegador consegue executar.

O pacote fastapi-vue pode, no entanto, ser usado com outros também, então vou descrever brevemente os concorrentes.

React

O React é o gorila de 360 quilos. Tem um ecossistema colossal, uma resposta para tudo, e geralmente mais três respostas discutindo com a primeira.

A sua maior vantagem prática é simplesmente a gravidade: muitos desenvolvedores o conhecem, muitas bibliotecas o têm como alvo, e muito código de exemplo começa com npm install react. É imensamente mais popular do que o Vue, o que torna a integração relativamente frágil entre React e FastAPI bastante reveladora por si só.

Até mesmo a solução full-stack oficial do FastAPI — lançada há apenas algumas semanas — é fundamentalmente um repositório de template: uma stack React pré-selecionada da qual você parte, em vez de uma ferramenta capaz de entrar em uma aplicação Python existente e integrar um frontend a ela.

Svelte

O Svelte adota uma abordagem mais voltada ao compilador. Em vez de enviar tanta maquinaria de framework para o navegador, ele transforma os componentes em tempo de build. O resultado ainda pode rodar sem Node em produção.

Ele exige um pouco de truques para uma reatividade correta, requerendo $ em sua linguagem customizada para indicar o que precisa de atualização. Por outro lado, essa linguagem customizada permite um código mais curto do que o de seus concorrentes.

Vue

Vue fica num lugar de que eu gosto bastante: estrutura suficiente para construir aplicações substanciais, mas sem fazer o HTML desaparecer sob várias camadas geológicas de JavaScript. A reatividade com a sua store Pinia Simplesmente Funciona, e não atrapalha. E quando você precisa daquele último bocado absoluto de performance, ele integra bem com estruturas não reativas. Sem wrapping complicado nem boilerplate de useState e afins.

E ele roda rápido, o que não é o caso da maioria dos apps React.

Sua ferramenta oficial create-vue também me oferece algo particularmente útil aqui: eu não preciso inventar minha ideia de como um projeto Vue deveria ser. Ela pode perguntar interativamente se a aplicação deve usar TypeScript, Vue Router, Pinia, Vitest, Playwright/Cypress, ESLint, Prettier e o resto do equipamento usual.

Isso se torna importante mais tarde.

Vue-Svelte-React
Resolvendo um formulário web mínimo que aparece com frequência em aplicações: um dropdown para escolher um registro a editar, com inputs para seus campos. O Svelte tem o código mais curto, mas o Vue é um index.html puro de navegador e, na verdade, tem o mesmo tamanho. E então vem o React, com trabalho demais para caber numa captura de tela. Todas as vagas estão em React e esta imagem mostra o porquê.

Como fazemos o deploy disso, na prática?

Uma vez que eu tenho o FastAPI de um lado e Vue, React ou Svelte do outro, existem algumas maneiras fundamentalmente diferentes de colocar a criatura resultante em um servidor.

Opção 1: render-se ao Node

A resposta óbvia no mundo JavaScript é colocar um servidor JavaScript em produção também.

Isso faz todo o sentido se eu realmente quiser um backend em Node. Há uma vantagem genuína em ter uma única linguagem e ferramentas intimamente relacionadas dos dois lados do fio. Se a minha equipe quer TypeScript em tudo, essa é uma arquitetura perfeitamente coerente.

Eu não.

Escolhi o FastAPI porque quero escrever o backend em Python. Instalar outro runtime ao lado dele só para entregar os assets do frontend é como contratar um segundo chef só para levar os pratos da cozinha até o salão.

image
Será que eu quero gigabytes de node_modules e isso no meu servidor de produção? Não.

Há também o pequeno problema do universo de dependências do JavaScript. Ele é pesado demais, mantém pouca compatibilidade e está frequentemente quebrado. Embora o nvm consiga instalar facilmente a versão correta do Node, ele ainda ocupa uma quantidade enorme de espaço que servidores hospedados muitas vezes não têm.

O Node é útil na minha máquina de desenvolvimento. O Vite é excelente. O create-vue é excelente. O ecossistema de build cumpre seu papel extremamente bem.

Isso não significa que eu queira qualquer coisa disso na máquina de produção.

Opção 2: renderizar tudo no servidor

No extremo oposto, posso dispensar totalmente o SPA e gerar HTML em Python.

Isto merece mais respeito do que às vezes recebe. Nem toda aplicação precisa de um runtime de aplicação no cliente, um router e vários megabytes de civilização de suporte.

Ferramentas como o htmx podem levar o HTML renderizado no servidor surpreendentemente longe, adicionando requisições HTTP, swaps, transições, WebSockets e server-sent events diretamente por meio de atributos HTML. Combine com o html5tagger para criar esses documentos e fragmentos de HTML no servidor, e você terá um site que roda em qualquer lugar e que pode ser lido tanto por redes sociais, mecanismos de busca e agentes.

Para sites dominados por documentos, formulários e interações relativamente simples, eu gosto dessa arquitetura. O navegador requisita algo, o Python produz HTML, e ninguém precisa de um pipeline de build capaz de lançar uma sonda a Marte.

O Vasanko.com é construído com esse método, adicionando Vue apenas para a interface administrativa, onde é necessária uma interatividade mais forte. Este CMS roda em um setup FastAPI–Vue, mas renderiza todas as páginas no servidor, em Python.

Opção 3: rodar dois servidores

O compromisso comum é, portanto:

  • O FastAPI executa a API.
  • O Vite ou outro servidor JavaScript executa o frontend
  • Um proxy reverso coloca ambos sob um mesmo hostname.
  • A produção agora tem duas aplicações, duas pilhas de dependências, dois processos e mais um arquivo de configuração segurando o casamento.

Isso funciona, mas não é bonito.

O navegador não se importa se o app.js foi emitido por Python, Node, Caddy, nginx ou uma torradeira suficientemente determinada. Depois que o frontend foi construído, o resultado é HTML, CSS, JavaScript, fontes e imagens estáticos.

Então por que manter a fábrica funcionando depois que o produto já saiu da linha de montagem?

Construa com JavaScript, execute com Python

Essa se tornou a ideia central por trás do fastapi-vue-setup.

Quero as ferramentas JavaScript onde elas são realmente úteis:

  • create-vue para criar o frontend
  • Vite para o servidor de desenvolvimento
  • Hot reload instantâneo nas alterações
  • O build normal do Vue em Node, Bun ou Deno

E então eu quero que ele desapareça.

O artefato de produção deve ser um pacote Python contendo um frontend já buildado. A instalação desse pacote não deve exigir Node, npm, Vite, código-fonte Vue, nem a anomalia gravitacional tradicionalmente armazenada em node_modules.

É exatamente assim que o fastapi-vue-setup constrói projetos hoje: o uv build executa o build do frontend e inclui os assets resultantes dentro do pacote Python. A configuração Hatch gerada trata o frontend-build como um artefato e acopla o hook no build do source distribution, enquanto limita o conteúdo do pacote ao próprio pacote Python.

A distinção importa especialmente para um sdist. Não quero que um source distribution signifique “aqui está um pouco de Python, aqui está uma árvore de desenvolvimento Vue completa, agora por favor instale o Node e reconstrua a aplicação real você mesmo”. O frontend já está compilado antes de a distribuição sair da minha máquina.

O pacote contém o que ele precisa para rodar, não tudo o que eu por acaso precisei para compilá-lo.

Isso deixa o deployment com um aspecto maravilhosamente entediante:

uvx my-app

Sem cerimônia de instalação do Node na máquina de destino. Sem npm install. Sem servidor frontend. Sem árvore de dependências JavaScript acordando três meses depois e pedindo para ser alimentada.

O pacote inicia o FastAPI, e o FastAPI serve a aplicação.

O outro problema feio

Construir o frontend foi apenas metade do trabalho.

StaticFiles não é exatamente um servidor frontend

O FastAPI expõe o StaticFiles do Starlette, e para assets estáticos comuns ele faz exatamente o que promete:

app.mount("/static", StaticFiles(directory="static"))

O problema começa quando o frontend é o próprio site.

Uma aplicação Vue normalmente quer /. Ela também pode querer /login, /settings, /dashboard/coffee-reactor/7 e qualquer outra coisa gerenciada pelo router do lado do cliente.

Mas montar StaticFiles em / efetivamente entrega a essa aplicação montada todo o espaço restante de URLs. O próprio mantenedor do FastAPI explicou o problema anos atrás: monte na raiz e a aplicação estática assume o controle, de modo que as operações de path normais abaixo dela deixam de funcionar como esperado.

E precisamos de:

  • Build de frontend em /*
  • O FastAPI ainda cuida de
    • /api/...
    • /openapi.json
    • quaisquer outras rotas arbitrárias

Este não é o mesmo problema que servir /static/logo.svg.

O FastAPI melhorou muito recentemente o seu próprio suporte a frontend, e a documentação atual agora aponta explicitamente as aplicações frontend para app.frontend() em vez do simples StaticFiles. Mas o fastapi-vue é anterior a essa solução e também tem um trabalho um tanto diferente: é a pequena metade de runtime deste sistema completo de empacotamento.

Runtime do fastapi-vue

O pacote complementar fastapi-vue fornece um handler Frontend personalizado em vez de montar StaticFiles sobre a aplicação.

No modo SPA, ele pode retornar index.html para caminhos pertencentes ao router do lado do cliente, em vez de exigir um arquivo físico com esse nome.

Note

Devido ao funcionamento do roteamento do FastAPI, os mounts do app engolem tudo sob aquele caminho. O restante das rotas é tentado em ordem e a primeira vence. Portanto, no modo SPA devemos colocar o catch-all por último no módulo do app.

Com o modo SPA desativado, ele só se vincula a caminhos de arquivos reais, permitindo que suas rotas seguintes ainda capturem o que passar.

Ele também cuida dos detalhes menos glamurosos que eu não tenho particularmente vontade de reimplementar toda terça-feira:

  • ETag e Last-Modified
  • Cache imutável para assets compilados
  • Cache em RAM com compressão zstd
  • Fallback de roteamento SPA e /favicon.ico quando necessário
  • Não servir build desatualizado acidental em modo dev

Esta é a dependência de runtime de produção. É pequena e apenas Python.

Toda a maquinaria para compilar e desenvolver a aplicação Vue permanece no projeto fonte.

Apresentando o fastapi-vue-setup

Com essas peças no lugar, o fastapi-vue-setup se preocupa principalmente em eliminar a fiação repetitiva.

O comando básico é propositalmente sem graça: aponte-o para a pasta do seu app, ou . se você já estiver nela.

uvx fastapi-vue-setup my-app

Need to change the port numbers your prod and vite and dev backend use by default? Run with --ports. Without that it retains the ports you previously had configured. And you can always --listen on your CLI or scripts/devserver.py to change that at runtime.

Mas há dois casos bem diferentes escondidos por trás disso.

E ele fornece utilitários de suporte para o ponto de entrada CLI que sua aplicação recebe, que pode então também aceitar suas próprias opções de linha de comando, algo que o fastapi run não consegue oferecer.

Criando uma nova aplicação

Para um novo projeto, não quero que o fastapi-vue-setup imponha um template Vue congelado criado por mim. Quero que ele crie um projeto Python configurado com o nome que eu dei, e com o setup Vue que eu escolher:

  • JavaScript ou TypeScript
  • Vue Router ou sem router
  • Pinia ou não
  • Opções de testes, linting e formatação
  • As outras opções suportadas pelo create-vue atual

A ferramenta então constrói a integração com o FastAPI em torno da aplicação que o desenvolvedor realmente escolheu.

Isso importa porque um template inevitavelmente fossiliza as preferências de alguém. Seis meses depois, sua ideia de um full stack moderno pode já pertencer a uma vitrine de museu, e você fica preso ao que recebeu.

Eu prefiro ter a escolha.

Configuração concluída

Já tem um aplicativo?

Aqui eu já posso ter um projeto real contendo código que eu preferiria não vaporizar. Ele pode ser criado com o mesmo script ou existir de forma independente, e o script fará os patches necessários onde for preciso.

O script de setup detecta o projeto Python e o módulo backend, detecta ou cria o frontend Vue, e aplica patches nas partes que ele consegue integrar com segurança e alterações mínimas.

Note

Mova seu Vue existente completamente para dentro de frontend/ primeiro. Colocamos o Vue ali para evitar poluir a raiz com coisas do Node. Coloque-o ali para que o script aplique patches no app existente em vez de criar um novo.

Precisa atualizar para a versão mais recente? Basta executar fastapi-vue-setup novamente e ele aplicará os patches dos novos recursos que foram adicionados.

Essa é uma das principais diferenças entre isto e um repositório de template.

Um template diz:
Faça um fork deste repositório para iniciar seu projeto

Eu precisava de uma ferramenta que também pudesse dizer:
Tudo bem, você já está com 30.000 linhas escritas. Mostre-me onde está o paciente.

Seu app está pronto para rodar

uv run scripts/devserver.py

vite.config.js

Configure which paths are proxied to the backend. By default this just /api. This configuration only affects the dev setup, in production everything lands on FastAPI.

JS_RUNTIME

Environment with value bun/deno/node or path to one of them (otherwise we find one).

Isso inicia o servidor de desenvolvimento Vue/Vite e o FastAPI com suporte a reload. A integração do Vite gerada faz proxy das requisições do backend para o servidor de desenvolvimento Python. Seu navegador se conecta ao Vite.

Assim, durante o desenvolvimento, eu ainda aproveito as coisas de que realmente gosto no ecossistema JavaScript.

Olá, mundo conectando FastAPI

Quando o desenvolvimento estiver concluído:

uv build  # Installs and (re)builds everything
my-app    # CLI entry point provided (in .venv)

O Vue é compilado, o resultado é embutido na distribuição Python, e a maquinaria de build cumpriu sua missão. Você pode rodar uv publish no seu pacote, se assim desejar, ou simplesmente copiar para produção a partir de dist/, e então instalar e executar com:

uv tool install my-app-0.1.0.tar.gz
my-app

Tudo o que eu precisava instalar antes era o UV e, no sistema de desenvolvimento, o próprio Node. Não precisei tocar no npm nem uma vez e agora posso instalá-lo ou executá-lo em outro lugar sem carregar a oficina de JavaScript junto.

Uma aplicação, finalmente

O que eu realmente queria não era particularmente exótico.

  • Eu queria escrever o backend em Python.
  • Eu queria escrever o frontend em Vue.
  • Eu queria o Vite durante o desenvolvimento.

E eu queria configurar e implantar uma coisa rapidamente.

Não uma aplicação Python somada a uma aplicação JavaScript. Não dois containers unidos por nginx e desconfiança mútua.

Um pacote Python de <1MB que Simplesmente Funciona™.

Ele contém o frontend. O FastAPI o serve a partir da raiz do site sem engolir o restante da aplicação. O roteamento do Vue funciona quando quero uma SPA, e o roteamento simples por arquivos permite que meu backend mantenha os catch-alls para si, por exemplo, tratando qualquer URL amigável com conteúdo renderizado no servidor, como neste site. Este, por sua vez, pode usar Vue nos lugares em que precisa, dois mundos em perfeita harmonia.

A toolchain de JavaScript faz o que uma toolchain deve fazer:

ela compila o software e depois sai do caminho.

Em seguida, seu full stack precisa de um banco de dados e o Kanta tem tudo o que você precisa. Com essa toolchain, seu app estará rodando rapidinho.