FastAPI gaat Full Stack
Het verhaal van hoe Vue de slang zag en verliefd werd.
FastAPI is een van die stukjes software die me achterdochtig maken, omdat het Python-backendverhaal te aangenaam is. Type hints in Flask/Express-stijl handlers worden parsing en validatie. Async met auto reloads werkt zonder een eldritch-achtige frameworkconfiguratiehandleiding tevoorschijn te toveren. Gooi er de OpenAPI-schemas en auto reloads bij en je gaat van git init naar een respectabele API nog voordat het koffiezetapparaat klaar is met opstarten.
En dan heb je een frontend nodig.
Historisch gezien eindigde hier de neonverlichte expressweg abrupt tegen een betonnen muur.
FastAPI is heel goed in API’s, maar het kan je frontend niet echt serveren op de root van je site, tenzij je alle requests naar StaticFiles wilde routeren. En SPA-ondersteuning is ongehoord. Je kon bestanden serveren, templates renderen of welke machinery dan ook aankoppelen, maar de laatste mijl tussen een moderne JavaScript-frontend en de Python-applicatie werd grotendeels als oefening voor de lezer gelaten.
En die lezer was steeds vaker ik.
Ondertussen, in frontendland
Ik gebruik hiervoor Vue. Vue beweegt zich in grofweg dezelfde arena als React en Svelte: component-gebaseerde interfaces, reactieve state, client-side routing en de gebruikelijke moderne machinerie om een stapel bronbestanden om te zetten in iets dat een browser kan uitvoeren.
Het fastapi-vue-pakket kan echter ook met anderen gebruikt worden, dus ik beschrijf de kandidaten kort.
React
React is de 800-pond-gorilla. Het heeft een kolossaal ecosysteem, een antwoord op alles, en meestal nog drie extra antwoorden die het oneens zijn met het eerste.
Het grootste praktische voordeel is simpelweg zwaartekracht: veel developers kennen het, veel libraries richten zich erop, en veel voorbeeldcode begint met npm install react. Het is enorm veel populairder dan Vue, wat de relatief dunne integratie tussen React en FastAPI op zichzelf al veelzeggend maakt.
Zelfs de officiële FastAPI full-stack-oplossing — pas enkele weken geleden voor het eerst uitgebracht — is in de kern een template repository: een voorgeselecteerde React-stack waar je vanuit vertrekt, in plaats van tooling die een bestaande Python-applicatie binnenstapt en er een frontend aan koppelt.
Svelte
Svelte kiest een aanpak die zwaarder op de compiler leunt. In plaats van zoveel framework-machinerie naar de browser te sturen, transformeert het componenten tijdens de build. Het resultaat kan in productie alsnog zonder Node draaien.
Het vereist wat trucjes voor correcte reactiviteit, waarbij $ in zijn eigen taal nodig is om aan te geven wat een update nodig heeft. Aan de andere kant maakt deze eigen taal kortere code mogelijk dan bij de concurrenten.
Vue
Vue zit op een plek die me goed bevalt: genoeg structuur om substantiële applicaties te bouwen, maar zonder dat de HTML verdwijnt onder meerdere geologische lagen JavaScript. Reactiviteit met de Pinia-store werkt gewoon, en blijft uit de weg. En als je dat laatste restje performance nodig hebt, integreert het goed met niet-reactieve structuren. Geen ingewikkelde wrapping of useState en dergelijke boilerplate nodig.
En het draait snel, wat niet het geval is bij de meeste React-apps.
De officiële create-vue-tool geeft me hier ook iets bijzonder nuttigs: ik hoef niet mijn eigen idee te bedenken van hoe een Vue-project eruit zou moeten zien. De tool kan interactief vragen of de applicatie TypeScript, Vue Router, Pinia, Vitest, Playwright/Cypress, ESLint, Prettier en de rest van de gebruikelijke uitrusting moet gebruiken.
Dat wordt later belangrijk.
Hoe deployen we dit ding eigenlijk?
Zodra ik FastAPI aan de ene kant heb en Vue, React of Svelte aan de andere, zijn er een paar fundamenteel verschillende manieren waarop ik het resulterende wezen op een server kan zetten.
Optie 1: capituleer voor Node
Het voor de hand liggende antwoord uit de JavaScript-wereld is om ook in productie een JavaScript-server te draaien.
Dat is heel logisch als ik daadwerkelijk een Node-backend wil. Er is een echt voordeel aan één taal en nauw verwante tooling aan beide kanten van de verbinding. Als mijn team overal TypeScript wil, is dat een perfect coherente architectuur.
Ik niet.
Ik koos FastAPI omdat ik de backend in Python wil schrijven. Een extra runtime ernaast installeren alleen om frontend-assets te leveren voelt als het aannemen van een tweede chef om borden van de keuken naar de eetzaal te dragen.
Daarnaast is er nog de kleine kwestie van het JavaScript-dependency-universum. Het is te zwaar, kent weinig compatibiliteit en is vaak kapot. Hoewel nvm eenvoudig de juiste Node-versie kan installeren, neemt het alsnog enorm veel ruimte in beslag die gehoste servers vaak niet hebben.
Node is nuttig op mijn ontwikkelmachine. Vite is uitstekend. create-vue is uitstekend. Het build-ecosysteem doet zijn werk buitengewoon goed.
Dat betekent niet dat ik er iets van op de productiemachine wil hebben.
Optie 2: alles op de server renderen
Aan de andere uiterste kan ik de SPA volledig overslaan en HTML in Python genereren.
Dit verdient meer respect dan het soms krijgt. Niet elke applicatie heeft een client-side applicatie-runtime, een router en enkele megabytes aan bijbehorende beschaving nodig.
Tools zoals htmx kunnen server-rendered HTML verrassend ver brengen door HTTP-requests, swaps, transitions, WebSockets en server-sent events direct via HTML-attributen toe te voegen. Combineer dit met html5tagger om die documenten en HTML-fragmenten op de server te genereren, en je hebt een site die overal draait en die door sociale media, zoekmachines en agents gelezen kan worden.
Voor sites die gedomineerd worden door documenten, formulieren en relatief eenvoudige interacties, vind ik deze architectuur prettig. De browser vraagt iets op, Python produceert HTML, en niemand heeft een build pipeline nodig waarmee je een Mars-sonde zou kunnen lanceren.
Vasanko.com is gebouwd volgens deze methode, waarbij Vue alleen wordt toegevoegd voor de admin-interface waar meer interactiviteit nodig is. Dit CMS draait op een FastAPI–Vue-setup, maar rendert alle pagina’s serverside in Python.
Optie 3: draai twee servers
Het gebruikelijke compromis is daarom:
- FastAPI draait de API.
- Vite of een andere JavaScript-server draait de frontend
- Een reverse proxy zet beide onder één hostname.
- De productieomgeving heeft nu twee applicaties, twee dependency-stacks, twee processen en nog een configuratiebestand dat het huwelijk bij elkaar houdt.
Dit werkt, maar het is niet mooi.
De browser maakt het niet uit of app.js door Python, Node, Caddy, nginx of een voldoende vastberaden broodrooster is geproduceerd. Zodra de frontend eenmaal gebouwd is, is het resultaat statische HTML, CSS, JavaScript, lettertypen en afbeeldingen.
Waarom zou je de fabriek draaiende houden als het product al van de lopende band is gerold?
Bouwen met JavaScript, draaien met Python
Dat werd het centrale idee achter fastapi-vue-setup.
Ik wil JavaScript-tooling daar waar het daadwerkelijk nuttig is:
create-vueom de frontend aan te maken- Vite voor de development server
- Directe hot reloads bij wijzigingen
- De normale Vue-build op Node, Bun of Deno
En dan wil ik het weg hebben.
Het productie-artifact moet een Python-package zijn met een reeds gebouwde frontend. Het installeren van die package mag geen Node, npm, Vite, Vue-broncode of de gravitatie-anomalie die traditioneel onder node_modules wordt opgeslagen vereisen.
Dat is precies hoe fastapi-vue-setup projecten vandaag bouwt: uv build voert de frontend-build uit en neemt de resulterende assets op in het Python-package. De gegenereerde Hatch-configuratie behandelt frontend-build als een artifact en koppelt aan de build van de source-distributie, terwijl de inhoud van het package beperkt blijft tot het Python-package zelf.
Het onderscheid is vooral belangrijk voor een sdist. Ik wil niet dat een source-distributie betekent: “hier is wat Python, hier is een complete Vue-ontwikkelboom, installeer nu zelf maar Node en reconstrueer de eigenlijke applicatie.” De frontend is al gecompileerd voordat de distributie mijn machine verlaat.
Het pakket bevat wat het nodig heeft om te draaien, niet alles wat ik toevallig nodig had om het te bouwen.
Zo ziet de deployment er heerlijk saai uit:
uvx my-app
Geen Node-installatieceremonie op de doelmachine. Geen npm install. Geen frontend-server. Geen JavaScript-dependency-boom die drie maanden later wakker wordt en om eten vraagt.
Het pakket start FastAPI, en FastAPI serveert de applicatie.
Het andere lelijke probleem
Het bouwen van de frontend was maar de helft.
StaticFiles is niet helemaal een frontend-server
FastAPI levert Starlette’s StaticFiles mee, en voor gewone statische assets doet het precies wat het belooft:
app.mount("/static", StaticFiles(directory="static"))
De problemen beginnen wanneer de frontend de site zelf is.
Een Vue-applicatie wil normaal gesproken /. Ze kan ook /login, /settings, /dashboard/coffee-reactor/7 en al het andere willen dat door de client-side router wordt beheerd.
Maar het mounten van StaticFiles op / geeft die gemounte applicatie in feite de volledige resterende URL-ruimte in handen. De maintainer van FastAPI zelf legde het probleem jaren geleden al uit: mount hem op de root en de statische applicatie neemt het over, waardoor de normale path operations eronder niet meer werken zoals verwacht.
En we hebben nodig:
- Frontend-build op
/* - FastAPI handelt nog steeds af
/api/.../openapi.json- alle willekeurige andere routes
Dit is niet hetzelfde probleem als het serveren van /static/logo.svg.
FastAPI heeft zeer recentelijk zijn eigen frontend-ondersteuning verbeterd, en de huidige documentatie verwijst frontend-applicaties nu expliciet naar app.frontend() in plaats van naar gewone StaticFiles. Maar fastapi-vue dateert van vóór die oplossing en heeft ook een enigszins andere taak: het is de kleine runtime-helft van dit complete packaging-systeem.
Runtime fastapi-vue
Het bijbehorende fastapi-vue-pakket biedt een aangepaste Frontend-handler in plaats van StaticFiles over de applicatie te mounten.
In SPA-modus kan het index.html teruggeven voor paden die bij de client-side router horen, in plaats van dat er een fysiek bestand met die naam vereist is.
Note
Vanwege hoe FastAPI-routing werkt, slikken app-mounts alles onder dat pad op. De overige routes worden op volgorde geprobeerd en de eerste die matcht wint. Daarom moeten we in SPA-modus de catch-all als laatste in de app-module plaatsen.
Met SPA-modus uitgeschakeld bindt het alleen aan paden van bestaande bestanden, waardoor routes erna nog steeds alles kunnen opvangen wat erdoorheen valt.
Het regelt ook de minder glamoureuze details die ik niet bepaald elke dinsdag opnieuw wil implementeren:
ETagenLast-Modified- Onveranderlijke caching voor gebouwde assets
- RAM-caching met zstd-compressie
- SPA-routing-fallback en
/favicon.icowanneer nodig - Geen per ongeluk verouderde build serveren in devmodus
Dit is de runtime-afhankelijkheid voor productie. Deze is klein en alleen Python.
Alle machinery voor het bouwen en ontwikkelen van de Vue-applicatie blijft in het bronproject.
Maak kennis met fastapi-vue-setup
Met die onderdelen op hun plaats richt fastapi-vue-setup zich vooral op het wegnemen van de repetitieve bedrading.
Het basiscommando is bewust onopwindend: wijs het naar je app-map, of naar . als je er al in zit.
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.
Maar er schuilen twee behoorlijk verschillende gevallen achter.
En het biedt ondersteunende utilities voor het CLI-entrypoint dat je app krijgt, waaraan je vervolgens ook je eigen commandoregelopties kunt toevoegen — iets wat fastapi run niet kan bieden.
Een nieuwe applicatie aanmaken
Voor een nieuw project wil ik niet dat fastapi-vue-setup een bevroren Vue-template van mijn eigen hand oplegt. Ik wil dat het een Python-project aanmaakt, geconfigureerd met de naam die ik heb opgegeven, en de Vue-setup die ik kies:
- JavaScript of TypeScript
- Vue Router of geen router
- Pinia of niet
- Keuzes voor testing, linting en formatting
- De overige opties die door de huidige
create-vueworden ondersteund
De tool bouwt vervolgens de FastAPI-integratie rond de applicatie die de developer daadwerkelijk heeft gekozen.
Dit is belangrijk omdat een template onvermijdelijk iemands voorkeuren vereeuwigt. Zes maanden later hoort het idee van een moderne full stack misschien al in een museumvitrine thuis, en zit jij vast aan wat je hebt gekregen.
Ik heb liever de keuze.
Heeft u al een app?
Hier heb ik mogelijk al een echt project met code die ik liever niet wil laten verdampen. Het kan met hetzelfde script zijn aangemaakt of op zichzelf staan, en het script zal het waar nodig patchen.
Het setup-script detecteert het Python-project en de backend-module, detecteert of maakt de Vue-frontend aan, en patcht de onderdelen die het veilig kan integreren met minimale wijzigingen.
Note
Verplaats je bestaande Vue eerst volledig naar frontend/. We plaatsen Vue daar om te voorkomen dat de root vervuild raakt met Node-zaken. Plaats het daar zodat het script de bestaande app patcht in plaats van een nieuwe aan te maken.
Moet je upgraden naar de nieuwste versie? Voer gewoon fastapi-vue-setup opnieuw uit en het patcht de nieuwe features erin.
Dat is een van de grote verschillen tussen dit en een template-repository.
Een template zegt:
Fork deze repository om je project te startenIk had tooling nodig die ook kan zeggen:
Prima, je zit al op 30.000 regels. Laat me zien waar de patiënt is.
Je app is klaar om te draaien
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).
Dat start de Vue/Vite-developmentserver en FastAPI met reload-ondersteuning. De gegenereerde Vite-integratie proxyt backend-requests naar de Python-developmentserver. Je browser maakt verbinding met Vite.
Zo krijg ik tijdens de ontwikkeling toch de dingen die ik prettig vind aan het JavaScript-ecosysteem.
Wanneer de ontwikkeling is afgerond:
uv build # Installs and (re)builds everything
my-app # CLI entry point provided (in .venv)
Vue is gecompileerd, het resultaat is in de Python-distributie gebakken, en de buildmachinery heeft zijn missie volbracht. Je kunt je pakket desgewenst publiceren met uv publish, of het gewoon vanuit dist/ naar prod kopiëren en vervolgens installeren en draaien met:
uv tool install my-app-0.1.0.tar.gz
my-app
Het enige wat ik vooraf hoefde te installeren was UV en op het dev-systeem Node zelf. Ik hoefde npm helemaal niet aan te raken en nu kan ik het ergens anders installeren of draaien zonder de JavaScript-werkplaats mee te sjouwen.
Eindelijk één applicatie
Wat ik uiteindelijk wilde was niet bijzonder exotisch.
- Ik wilde de backend in Python schrijven.
- Ik wilde de frontend in Vue schrijven.
- Ik wilde Vite gebruiken tijdens het ontwikkelen.
En ik wilde snel één ding opzetten en deployen.
Geen Python-applicatie plus een JavaScript-applicatie. Geen twee containers die door nginx en wederzijds wantrouwen aan elkaar zijn verbonden.
Een <1MB Python-package dat gewoon werkt™.
Het bevat de frontend. FastAPI serveert die vanaf de root van de site zonder de rest van de applicatie te verzwelgen. Vue-routing werkt wanneer ik een SPA wil, en gewone file-routing stelt mijn backend in staat catch-alls voor zichzelf te houden, bijvoorbeeld om elke pretty URL af te handelen met server side gerenderde content zoals op deze site. Die kan op zijn beurt weer Vue gebruiken waar nodig — twee werelden in perfecte harmonie.
De JavaScript-toolchain doet wat een toolchain hoort te doen:
het bouwt de software en houdt zich daarna op de achtergrond.
Vervolgens heeft je volledige stack een database nodig en Kanta staat voor je klaar. Met deze toolchain heb je je app binnen no time draaiend.