AI & Automatisering22 maart 202612 min

Van idee naar productie in 3 weken: hoe we Evender bouwden met AI als co-developer

Drie weken, 310 commits, één volledig werkend event platform. Het verhaal van Evender — en wat AI daarin betekende.

Drie weken. 310 commits. Eén volledig werkend event platform — van leeg repository tot live op staging, inclusief multi-tenancy, betalingen, e-tickets, zaalplattegronden, een AI helpfunctie en een productieklare CI/CD pipeline.

Dit is het verhaal van hoe we Evender bouwden. En wat AI daarin betekende — eerlijk, zonder hyperbole.

Wat is Evender?

Evender is een multi-tenant event platform voor de Nederlandse markt. Organisaties kunnen er hun eigen ticketshop in inrichten — inclusief huisstijl, eigen domein, zaalplattegronden, producten, betaling via Adyen, en barcodescanning bij de ingang. Bovenop dat platform draaien losse tenants die ieder hun eigen storefront, admin en configuratie hebben.

Technisch: een NestJS API, twee Next.js 14 frontends (storefront en admin), een gedeeld Prisma schema op PostgreSQL, allemaal in één Turborepo monorepo. Deploy via Docker containers naar een eigen server, georkestreerd via GitHub Actions.

Dat is een forse applicatie. Normaal is zo'n fundament een werk van maanden. Wij deden het in drie weken.

Dag 1: van leeg naar fundament

Op 1 maart begon het met de eerste commit: feat: initial monorepo setup with Turborepo.

Dezelfde dag nog volgden:

  • Een volledig Prisma schema met seed data
  • De NestJS API scaffold met alle core modules
  • Twee Next.js frontends
  • Een design system met 16 componenten en Storybook

Dat klinkt ongeloofwaardig. Maar het is wat de git history laat zien. De aanpak: geen blanco pagina, maar directe samenwerking met Claude als co-developer. Niet als codegenerator die losse snippets uitspuugt — maar als pair programmer die meedenkt over architectuur, consistency en de "why" achter keuzes.

Dag 2 brachten we het authentication systeem (JWT, rollen, frontend), multi-tenancy, een productcatalogus met admin, een volledige shopping cart, checkout flow met dummy payment provider, en het orderbeheer met barcodesysteem en e-ticketgeneratie.

Achttien commits op één dag. Alles logisch gegroepeerd, alles beschrijvend. Niet omdat we zo snel typten — maar omdat we de structuur van tevoren doordacht hadden, en AI de implementatie kon uitvoeren terwijl wij de richting bepaalden.

De architectuurkeuzes

Een platform bouwen gaat over keuzes maken. We lopen de belangrijkste langs.

Monorepo met Turborepo — We wilden API, twee frontends en gedeelde packages in één repository. Turborepo zorgt voor slimme build-caching en parallelisatie. De package scope @evender/* houdt alles overzichtelijk. Nadeel: opstarttijd om het goed in te richten. Voordeel: wijzigingen in packages/shared worden automatisch opgemerkt door alle consumers.

NestJS voor de API — De dependency injection, module-architectuur en decorator-based guards van NestJS zijn ideaal als je security-lagen wilt toevoegen zonder spaghetti. De JwtAuthGuard is globaal actief; nieuwe endpoints zijn standaard beveiligd. Je gebruikt @Public() alleen bewust, en documenteert waarom.

Prisma als ORM — Type-safe queries, automatische migraties, en een schema dat als single source of truth dient voor zowel database als TypeScript types. Migraties lopen via CI met prisma migrate deploy — nooit handmatig in productie.

Multi-tenancy via subdomeinen — Elke tenant krijgt een eigen subdomain (of custom domain). De API lost de tenant op via de Host header; de frontend via NEXT_PUBLIC_PLATFORM_DOMAIN. Tenant-isolatie loopt via een TenantGuard — geen ad-hoc checks in services.

Security: van begin af aan ingebakken

Security is geen feature die je er achteraf bijplakt. We kozen ervoor het van dag één in de architectuur te verankeren.

Een kleine greep uit wat er in de commits zit:

  • Geen hardcoded secrets: config.getOrThrow() overal — geen fallback waarden, geen defaults. Als een secret ontbreekt, crasht de applicatie bewust bij opstart.
  • CORS via allowlist: origin: true is een veiligheidsrisico. We schakelden dat expliciet uit en zetten CORS_ORIGINS als environment variable.
  • Helmet middleware: HTTP security headers (X-Frame-Options, Content-Security-Policy, etc.) via NestJS Helmet — standaard aan, niet optioneel.
  • EXIF stripping: Gebruikers uploaden foto's. EXIF-data kan GPS-coördinaten bevatten. We strippen die via sharp.rotate() voor opslag.
  • SVG sanitization: SVGs kunnen executable JavaScript bevatten. We haalden SVG van de upload-whitelist af en sanitizen voor opslag.
  • Webhook secrets versleuteld: Adyen webhook secrets staan niet in plaintext in de database. Een migratiescript encrypteerde bestaande secrets; nieuwe worden nooit plaintext opgeslagen.
  • SSRF-guard: Externe HTTP-fetches (bijv. voor de AI theme wizard) lopen via assertPublicUrl() — een guard die requests naar interne IP-ranges blokkeert.
  • CSP headers: Zowel storefront als admin kregen expliciete Content-Security-Policy headers in de Next.js configuratie.

Het mooie van AI als co-developer in dit proces: elke keer dat we een nieuw endpoint of module bouwden, checkte Claude proactief of de juiste guards aanwezig waren. Niet als vervanging van een security review — maar als eerste filter die evidente gaten dicht voordat ze überhaupt in de codebase terechtkomen.

Testing: self-contained vanaf dag één

Testen met gedeelde testdata is een tijdbom. Je seed-script loopt vandaag, je test verwacht morgen data die er niet meer is, en je CI is kapot om redenen die niets met je code te maken hebben.

We kozen voor een principe dat we consequent doorzetten: e2e tests zijn self-contained. Ze maken in beforeAll hun eigen testdata aan, en ruimen die in afterAll op. Geen afhankelijkheid van seed data. Geen gedeelde state tussen tests.

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Seed data bestaat alleen voor development en CI test databases. Nooit in productie. De CI test job draait de seed met NODE_ENV=test — expliciet, niet impliciet.

AI hielp hier op twee manieren. Ten eerste bij het schrijven van de tests zelf — boilerplate voor setup en teardown is saai werk waarbij je snel fouten maakt. Ten tweede bij het opsporen van race conditions en timing-issues in asynchrone flows (zoals het vasthouden van stoelreserveringen tijdens checkout).

Deployment: Docker, GitHub Actions, staging-first

De deployment setup is eenvoudig in principe, maar de duivel zit in de details.

Elke app heeft een eigen Dockerfile. De GitHub Actions pipeline bouwt de images, pusht naar ghcr.io/nielsdigitalspirit/evender, en deployt naar de server. Er zijn twee triggers: de staging branch deployt naar staging.evender.nl, een git tag (v{x.y.z}) triggert de productiedeploy.

De git workflow is bewust gestructureerd:

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Hotfixes mogen direct op main; daarna een patch-tag bumpen.

Wat we leerden: build arguments voor Next.js (NEXT_PUBLIC_* variabelen) moeten al beschikbaar zijn tijdens de Docker build — niet alleen bij runtime. Dat kostte een handvol CI-fixes om goed te krijgen.

Multi-stage builds houden de images klein. De uploads-directory krijgt een persistent Docker volume zodat geuploade bestanden niet verdwijnen bij een deploy.

AI in het product zelf: de helpfunctie

Tot nu toe hadden we het over AI als tool bij het bouwen. Maar Evender gebruikt AI ook intern — in de helpfunctie voor admins.

De setup: een kennisbank van help-artikelen wordt verwerkt via een n8n workflow (getriggerd bij elke GitHub push naar de docs/help map). De teksten worden ingebed als vectoren via de Anthropic API en opgeslagen in een PostgreSQL tabel met de pgvector extensie.

Wanneer een admin op de helpknop klikt, opent een ChatWidget met streaming SSE. De vraag wordt omgezet naar een vector, de dichtstbijzijnde kennisfragmenten worden opgehaald (RAG), en Claude genereert een antwoord op basis van die context — streamed, zodat de gebruiker direct feedback ziet.

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Een tweede n8n workflow (getriggerd bij een nieuwe git tag) checkt automatisch of de release-inhoud consistent is met de documentatie — een soort geautomatiseerde release QA.

Beide workflows draaien op staging via n8n, een open-source workflow automation tool.

De eerlijke balans: wat AI wél en niet goed doet

AI maakt je sneller. Maar het vervangt geen oordeel.

Wat goed werkt:

  • Boilerplate genereren: DTOs, modules, guards, testsetups — consistent en snel
  • Consistentie bewaken: als je een patroon hebt (bijv. TenantGuard op alle tenant-endpoints), zorgt AI dat nieuwe endpoints dat patroon volgen
  • Refactoring: grote, mechanische wijzigingen (bijv. 72 formuliervelden van tooltips voorzien) zonder mentale belasting
  • Bugs opsporen in asynchroon code

Wat menselijke sturing nodig heeft:

  • Architectuurkeuzes: welke modules? welke abstracties? — dat zijn gesprekken, geen vragen
  • Security: AI helpt patterns volgen, maar begrijpt de business-context niet
  • Productkeuzes: wat bouwen we eigenlijk, en waarom?
  • Integraties: Adyen, barcodescanners, custom domains — echte systemen gedragen zich anders dan de documentatie

De meest waardevolle sessies waren niet "genereer dit" — maar "wat zijn de trade-offs als we X zo aanpakken?"

Resultaten

  • 310 commits in 21 werkdagen
  • Dag 1: leeg repository → complete monorepo scaffold
  • Dag 2: auth, multi-tenancy, catalog, cart, checkout, orders — alles
  • Week 2: security hardening, webhooks, CI/CD, helpfunctie met RAG
  • Week 3: seated events, zaalplattegronden, AI-gegenereerde SVGs, design system, storefront varianten

Een platform van deze omvang is normaal een werk van 4–6 maanden voor een klein team. Dat we het in 3 weken hebben kunnen neerzetten, is volledig te danken aan de combinatie van heldere architectuurkeuzes, gedisciplineerde git workflow, en AI als volwaardige pair programmer.

Wat andere teams hieruit kunnen leren

  1. Begin met structuur, niet met features. De eerste dag besteedden we aan monorepo setup, schema, en design system. Die investering betaalde zich de rest van het project terug.
  2. AI werkt het best als sparringpartner, niet als codegenerator. Denk hardop, leg context uit, vraag naar trade-offs. Je krijgt betere code én beter begrip.
  3. Security is een architectuurbeslissing. Globale guards, verplichte env vars, expliciete @Public() decorators — niet iets dat je achteraf toevoegt.
  4. Self-contained tests vanaf het begin. Het kost iets meer tijd per test, maar je CI is stabiel en je tests zijn betrouwbaar.
  5. AI in het product zelf kán — maar doe het doelgericht. De helpfunctie werkt omdat we een duidelijke use case hadden (snelle antwoorden op beheervragen) en de kennisbank goed gestructureerd is.

Benieuwd wat AI voor jouw project kan betekenen?

Plan een gesprek