13 dec 2025·6 min leestijd

Geëxporteerde Go-backends uitbreiden met veilige, aangepaste middleware

Geëxporteerde Go-backends uitbreiden zonder wijzigingen te verliezen: waar je aangepaste code plaatst, hoe je middleware en endpoints toevoegt en hoe je upgrades plant.

Geëxporteerde Go-backends uitbreiden met veilige, aangepaste middleware

Wat er misgaat wanneer je geëxporteerde code aanpast

Geëxporteerde code is niet hetzelfde als een handgeschreven Go-repo. Met platforms zoals AppMaster wordt de backend gegenereerd vanuit een visueel model (dataschema, businessprocessen, API-instellingen). Wanneer je opnieuw exporteert, kan de generator grote delen van de code herschrijven om bij het bijgewerkte model te passen. Dat is fijn om alles schoon te houden, maar het verandert hoe je moet customizen.

De meest voorkomende fout is het direct bewerken van gegenereerde bestanden. Het werkt één keer, en bij de volgende export overschrijft de generator je wijzigingen of veroorzaakt hij lelijke mergeconflicten. Nog erger: kleine handmatige aanpassingen kunnen stilletjes aannames van de generator breken (routingvolgorde, middleware-ketens, request-validatie). De app bouwt misschien nog, maar het gedrag verandert.

Veilige customisatie betekent dat je wijzigingen herhaalbaar en makkelijk te reviewen zijn. Als je de backend opnieuw kunt exporteren, je custom laag kunt toepassen en duidelijk ziet wat er veranderd is, zit je goed. Als elke upgrade voelt als archeologie, zit je fout.

Hier zijn de problemen die meestal optreden wanneer customisatie op de verkeerde plaats gebeurt:

  • Je aanpassingen verdwijnen na re-export, of je besteedt uren aan het oplossen van conflicten.
  • Routes verschuiven en je middleware draait niet meer waar je het verwacht.
  • Logica raakt gedupliceerd tussen het no-code model en de Go-code, en loopt later uiteen.
  • Een "one-line change" verandert in een fork die niemand wil aanraken.

Een eenvoudige regel helpt beslissen waar wijzigingen horen. Als de wijziging onderdeel is van businessgedrag dat niet-ontwikkelaars moeten kunnen aanpassen (velden, validatie, workflows, permissies), zet het dan in het no-code model. Als het infrastructuurgedrag is (custom auth-integratie, request logging, speciale headers, rate limits), zet het dan in een custom Go-laag die re-exports overleeft.

Voorbeeld: audit logging voor elke request is meestal middleware (custom code). Een nieuw verplicht veld op een order hoort meestal bij het datamodel (no-code). Hou die scheiding duidelijk en upgrades blijven voorspelbaar.

Breng de codebase in kaart: gegenereerde delen versus jouw delen

Voordat je een geëxporteerde backend uitbreidt, besteed 20 minuten aan het in kaart brengen wat bij een re-export opnieuw gegenereerd wordt en wat je echt bezit. Die kaart houdt upgrades saai.

Gegenereerde code verraadt zichzelf vaak: headercomments zoals "Code generated" of "DO NOT EDIT", consistente naamgevingspatronen en een zeer uniforme structuur met weinig menselijke opmerkingen.

Een praktische manier om de repo te classificeren is alles in drie bakken te verdelen:

  • Gegenereerd (read-only): bestanden met duidelijke generatormarkers, herhaalde patronen of mappen die op een framework-skelet lijken.
  • Jouw eigendom: packages die je zelf maakte, wrappers en configuratie die jij beheert.
  • Gedeelde naden: wiring-punten bedoeld voor registratie (routes, middleware, hooks), waar kleine aanpassingen soms nodig zijn maar minimaal moeten blijven.

Behandel de eerste bak als read-only, ook al kun je het technisch gezien bewerken. Als je het verandert, ga er dan vanuit dat de generator het later zal overschrijven of dat je voor altijd een merge-last zult houden.

Maak de grens duidelijk voor het team door een korte notitie in de repo te zetten (bijvoorbeeld een root README). Houd het simpel:

"Generator-owned files: anything with a DO NOT EDIT header and folders X/Y. Our code lives under internal/custom (or similar). Only touch wiring points A/B, and keep changes there small. Any wiring edit needs a comment explaining why it can't live in our own package."

Die ene notitie voorkomt dat quick fixes veranderen in permanente upgrade-pijn.

Waar je custom code plaatst zodat upgrades simpel blijven

De veiligste regel is eenvoudig: behandel geëxporteerde code als read-only en zet je wijzigingen in een duidelijk eigen custom-gebied. Als je later opnieuw exporteert (bijvoorbeeld vanuit AppMaster), wil je dat de merge vooral is "replace generated code, keep custom code".

Maak een apart package voor je toevoegingen. Het kan in dezelfde repo leven, maar het moet niet door elkaar lopen met gegenereerde packages. De gegenereerde code draait de core-app; jouw package voegt middleware, routes en helpers toe.

Een praktisch layout-voorbeeld:

  • internal/custom/ voor middleware, handlers en kleine helpers
  • internal/custom/routes.go om custom routes op één plek te registreren
  • internal/custom/middleware/ voor request/response-logica
  • internal/custom/README.md met een paar regels voor toekomstige edits

Vermijd het bewerken van server-wiring op vijf verschillende plekken. Streef naar één dun "hook point" waar je middleware koppelt en extra routes registreert. Als de gegenereerde server een router of handlerketen exposeert, plug daar in. Zo niet, voeg dan één integratiebestand dicht bij de entrypoint toe dat iets aanroept als custom.Register(router).

Schrijf custom code alsof je het morgen in een gloednieuwe export moet stoppen. Hou dependencies minimaal, vermijd het kopiëren van gegenereerde types wanneer mogelijk, en gebruik kleine adapters.

Stapsgewijs: voeg veilige custom middleware toe

Het doel is om logica in je eigen package te plaatsen en gegenereerde code op slechts één plek te raken om het te koppelen.

Ten eerste: houd de middleware smal: request logging, een eenvoudige auth-check, een rate limit of een request-ID. Als het drie taken tegelijk probeert te doen, zul je later meer bestanden moeten veranderen.

Maak een klein package (bijvoorbeeld internal/custom/middleware) dat niet je hele app hoeft te kennen. Houd de publieke oppervlakte klein: één constructorfunctie die een standaard Go handler-wrapper teruggeeft.

package middleware

import "net/http"

func RequestID(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Add header, log, or attach to context here.
		next.ServeHTTP(w, r)
	})
}

Kies nu één integratiepunt: de plek waar de router of HTTP-server wordt aangemaakt. Registreer je middleware daar, één keer, en voorkom dat je wijzigingen over individuele routes verspreidt.

Houd de verificatielus kort:

  • Voeg één gerichte test toe met httptest die één resultaat checkt (statuscode of header).
  • Doe één handmatige request en bevestig het gedrag.
  • Controleer dat de middleware zich verstandig gedraagt bij fouten.
  • Voeg een korte comment dicht bij de registratie-regel toe die uitlegt waarom het bestaat.

Kleine diff, één wiringpunt, makkelijke re-exports.

Stapsgewijs: voeg een nieuw endpoint toe zonder alles te forken

Ship custom endpoints cleanly
Prototypeer een nieuw endpoint in AppMaster en koppel daarna een kleine custom handler op de juiste plaats.
Try Now

Behandel gegenereerde code als read-only en voeg je endpoint toe in een klein custom package dat de app importeert. Dat houdt upgrades redelijk.

Begin met het opschrijven van het contract voordat je code aanraakt. Wat accepteert het endpoint (queryparams, JSON-body, headers)? Wat geeft het terug (JSON-structuur)? Kies statuscodes vooraf zodat je niet eindigt met "wat werkte"-gedrag.

Maak een handler in je custom package. Houd het saai: lees input, valideer, roep bestaande services of database-helpers aan en schrijf een response.

Registreer de route op hetzelfde integratiepunt dat je voor middleware gebruikt, niet binnen gegenereerde handlerbestanden. Zoek waar de router tijdens startup wordt samengesteld en mount daar je custom routes. Als het gegenereerde project al hooks of custom-registratie ondersteunt, gebruik die.

Een korte checklist houdt gedrag consistent:

  • Valideer inputs vroeg (verplichte velden, formaten, min/max).
  • Geef overal één foutvorm terug (message, code, details).
  • Gebruik context-timeouts waar werk kan hangen (DB, netwerkcalls).
  • Log onverwachte fouten één keer en geef daarna een nette 500 terug.
  • Voeg een kleine test toe die de nieuwe route raakt en status en JSON controleert.

Controleer ook dat de router je endpoint precies één keer registreert. Dubbele registratie is een veelgemaakte post-merge valkuil.

Integratiepatronen die wijzigingen beperkt houden

Move from export to production
Deploy naar AppMaster Cloud of je eigen cloud nadat je exporteert en wijzigingen valideert.
Deploy App

Behandel de gegenereerde backend als een dependency. Geef compositie de voorkeur: wire features om de gegenereerde app heen in plaats van de corelogica te bewerken.

Geef config en compositie de voorkeur

Controleer eerst of het gedrag via configuratie, hooks of standaardcompositie toegevoegd kan worden. Middleware is een goed voorbeeld: voeg het aan de rand toe (router/HTTP-stack) zodat het verwijderd of opnieuw gerangschikt kan worden zonder businesslogica aan te raken.

Als je nieuw gedrag nodig hebt (rate limiting, audit logging, request IDs), houd het in je eigen package en registreer het vanuit een enkel integratiebestand. In een review moet het makkelijk uit te leggen zijn: "one new package, one registration point".

Gebruik adapters om gegenereerde types niet te laten lekken

Gegenereerde modellen en DTO's veranderen vaak bij exports. Om upgrade-pijn te verminderen, vertaal aan de rand:

  • Converteer gegenereerde request-types naar je eigen interne structs.
  • Voer domeinlogica uit met alleen je eigen structs.
  • Zet resultaten terug naar gegenereerde response-types.

Op die manier, als gegenereerde types verschuiven, wijst de compiler je naar één plek om aan te passen.

Als je echt gegenereerde code moet aanraken, isoleer het tot één wiring-bestand. Vermijd edits verspreid over veel gegenereerde handlers.

// internal/integrations/http.go
func RegisterCustom(r *mux.Router) {
    r.Use(RequestIDMiddleware)
    r.Use(AuditLogMiddleware)
}

Een praktische vuistregel: als je de wijziging niet in 2–3 zinnen kunt beschrijven, is het waarschijnlijk te verstrengeld.

Hoe je diffs over tijd beheersbaar houdt

Het doel is dat een re-export geen week aan conflicten wordt. Houd edits klein, makkelijk te vinden en eenvoudig uit te leggen.

Gebruik Git vanaf dag één en houd gegenereerde updates gescheiden van je custom werk. Als je ze mixt, weet je later niet wat een bug veroorzaakte.

Een commit-routine die leesbaar blijft:

  • Eén doel per commit ("Add request ID middleware", niet "misc fixes").
  • Meng geen alleen-formatteerwijzigingen met logicawijzigingen.
  • Na elke re-export commit je eerst de gegenereerde update, daarna je custom-aanpassingen.
  • Gebruik commit-berichten die het package of bestand noemen dat je aanraakte.

Houd een eenvoudige CHANGELOG_CUSTOM.md (of iets vergelijkbaars) bij met elke customisatie, waarom die bestaat en waar deze staat. Dit is vooral nuttig bij AppMaster-exports omdat het platform de code volledig kan regenereren en je snel wilt zien wat opnieuw toegepast of gevalideerd moet worden.

Verminder diff-noise met consistente formatting- en lintregels. Run gofmt bij elke commit en voer dezelfde checks in CI uit. Als gegenereerde code een bepaalde stijl gebruikt, "verbeter" die niet handmatig tenzij je voorbereid bent die verbetering na elke re-export te herhalen.

Als je team steeds dezelfde handmatige edits herhaalt na elke export, overweeg dan een patch-workflow: export, pas patches toe (of een script), run tests, deploy.

Plan upgrades: re-export, merge en valideer

Build internal tools faster
Bouw snel een internal tool of admin-portal en breid het daarna uit met custom Go-middleware.
Start Free

Upgrades zijn het makkelijkst als je de backend ziet als iets dat je kunt regenereren, niet als iets dat je voor altijd handmatig onderhoudt. Het doel is consistent: exporteer schone code en breng daarna je custom gedrag telkens via dezelfde integratiepunten aan.

Kies een upgrade-ritme dat past bij je risicotolerantie en hoe vaak de app verandert:

  • Per platformrelease als je snel securityfixes of nieuwe features nodig hebt
  • Per kwartaal als de app stabiel is en wijzigingen klein zijn
  • Alleen wanneer nodig als de backend zelden verandert en het team klein is

Als het tijd is om te upgraden, doe een dry-run re-export in een aparte branch. Bouw en draai de nieuw geëxporteerde versie eerst alleen, zodat je weet wat er veranderd is voordat je custom-laag erbij komt.

Breng daarna customizations weer aan via je geplande naden (middleware-registratie, custom routergroep, je custom package). Vermijd chirurgische edits binnen gegenereerde bestanden. Als een wijziging niet via een integratiepunt te maken is, is dat een signaal om éénmaal een nieuwe seam toe te voegen en die daarna altijd te gebruiken.

Valideer met een korte regressielijst gericht op gedrag:

  • Auth-flow werkt (login, token refresh, logout)
  • 3 tot 5 belangrijke API-endpoints geven dezelfde statuscodes en vormen terug
  • Eén unhappy path per endpoint (foute input, ontbrekende auth)
  • Achtergrondjobs of geplande taken draaien nog steeds
  • Health/readiness endpoint retourneert OK in je deployment setup

Als je audit-logging middleware toevoegde, controleer dan na elke re-export dat logs nog steeds user ID en routenaam bevatten voor één schrijfoperatie.

Veelgemaakte fouten die upgrades pijnlijk maken

De snelste manier om je volgende re-export te verpesten is het bewerken van gegenereerde bestanden "af en toe even". Het voelt onschuldig wanneer je een kleine bug fixt of een header-check toevoegt, maar maanden later weet je niet meer wat of waarom er iets veranderd is, en vaak genereert de generator nu andere output.

Een andere valkuil is het verspreiden van custom code: een helper in het ene package, een auth-check in een ander, een middleware-tweak bij routing en een one-off handler in een willekeurige map. Niemand voelt zich eigenaar en elke merge wordt een speurtocht. Houd wijzigingen in een klein aantal voor de hand liggende plekken.

Strakke koppeling aan gegenereerde internals

Upgrades worden pijnlijk wanneer je custom code afhankelijk is van gegenereerde interne structs, private velden of pakketindeling. Zelfs een kleine refactor in gegenereerde code kan je build breken.

Veiliger grenzen:

  • Gebruik request/response-DTOs die jij beheert voor custom endpoints.
  • Interageer met gegenereerde lagen via geëxporteerde interfaces of functies, niet via interne types.
  • Baseer middleware-beslissingen op HTTP-primitieven (headers, method, path) wanneer mogelijk.

Tests overslaan waar je ze het meest nodig hebt

Middleware- en routingbugs kosten tijd omdat failures eruit kunnen zien als willekeurige 401s of "endpoint not found". Een paar gerichte tests besparen uren.

Een realistisch voorbeeld: je voegt audit-middleware toe die de request body leest om die te loggen, en plots ontvangen sommige endpoints een lege body. Een kleine test die een POST door de router stuurt en zowel het audit-side-effect als het handlergedrag controleert, vangt die regressie en geeft vertrouwen na een re-export.

Korte pre-release checklist

Separate generated vs custom code
Maak businesslogica in no-code en houd aangepaste Go-wijzigingen in één eigen laag.
Start Building

Voordat je custom veranderingen shipp, doe een korte check die je beschermt bij de volgende re-export. Je moet exact weten wat opnieuw toegepast moet worden, waar het woont en hoe je het verifieert.

  • Hou alle custom code in één duidelijk benoemde package of map (bijv. internal/custom/).
  • Beperk touchpoints met gegenereerde wiring tot één of twee bestanden. Behandel ze als bruggen: registreer routes één keer, registreer middleware één keer.
  • Documenteer middleware-volgorde en waarom die zo is ("Auth before rate limiting" en waarom).
  • Zorg dat elk custom endpoint minstens één test heeft die bewijst dat het werkt.
  • Schrijf een herhaalbare upgrade-routine: re-export, breng custom-laag aan, run tests, deploy.

Als je maar één ding doet: schrijf de upgrade-notitie. Die verandert "ik denk dat het goed is" in "we kunnen bewijzen dat het nog werkt".

Voorbeeld: audit logging en een health-endpoint toevoegen

Make one clean integration seam
Maak één registratiepunt voor routes en middleware zodat re-exports eenvoudig blijven.
Try AppMaster

Stel dat je een Go-backend exporteerde (bijvoorbeeld vanuit AppMaster) en je wilt twee toevoegingen: een request ID plus audit logging voor admin-acties, en een simpele /health-endpoint voor monitoring. Het doel is dat je wijzigingen makkelijk opnieuw toepasbaar blijven na een re-export.

Voor audit logging zet je code in een duidelijk eigen plek zoals internal/custom/middleware/. Maak middleware die (1) X-Request-Id leest of genereert, (2) deze opslaat in de request context en (3) één korte auditregel logt voor admin-routes (method, path, user ID indien beschikbaar en resultaat). Hou het bij één regel per request en vermijd het wegschrijven van grote payloads.

Koppel het aan de rand, dicht bij waar routes worden geregistreerd. Als de gegenereerde router één setup-bestand heeft, voeg daar één kleine hook toe die je middleware importeert en alleen op de admin-groep toepast.

Voor /health voeg je een kleine handler toe in internal/custom/handlers/health.go. Geef 200 OK terug met een korte body zoals ok. Voeg geen auth toe tenzij je monitors dat nodig hebben. Documenteer het als je dat wel doet.

Structureer commits zodat ze makkelijk te herhalen zijn:

  • Commit 1: Voeg internal/custom/middleware/audit.go en tests toe
  • Commit 2: Koppel middleware aan admin-routes (zo klein mogelijke diff)
  • Commit 3: Voeg internal/custom/handlers/health.go toe en registreer /health

Na een upgrade of re-export verifieer de basics: admin-routes vereisen nog auth, request IDs verschijnen in admin-logs en /health reageert snel zonder merkbare latency onder lichte load.

Volgende stappen: stel een customisatie-workflow in die je kunt onderhouden

Behandel elke export als een frisse build die je kunt herhalen. Je custom code moet voelen als een add-on-laag, niet als een herschrijving.

Bepaal wat in code hoort versus in het no-code model. Businessregels, datavormen en standaard CRUD-logica horen meestal in het model. One-off integraties en bedrijfsspecifieke middleware horen meestal in custom code.

Als je AppMaster gebruikt (appmaster.io), ontwerp je custom werk als een schone extensielaag rond de gegenereerde Go-backend: houd middleware, routes en helpers in een klein aantal mappen die je kunt meenemen over re-exports, en raak generator-owned bestanden niet aan.

Een praktische eindcheck: als een collega kan re-exporten, jouw stappen toepassen en hetzelfde resultaat krijgt in minder dan een uur, is je workflow onderhoudbaar.

FAQ

Kan ik gewoon de geëxporteerde Go-bestanden direct bewerken?

Doe geen wijzigingen in door de generator beheerde bestanden. Zet je aanpassingen in een duidelijk eigen package (bijvoorbeeld internal/custom/) en verbind ze via één kleine integratiepunt bij server-startup. Zo vervangt een re-export meestal alleen de gegenereerde code en blijft jouw custom-laag intact.

Hoe zie ik welke delen van de geëxporteerde repo opnieuw gegenereerd worden?

Ga ervan uit dat alles met opmerkingen als “Code generated” of “DO NOT EDIT” herschreven wordt. Let ook op zeer uniforme mappenstructuren, repetitieve namen en weinig menselijke commentaar; dat zijn vingerafdrukken van een generator. Het veiligst is deze bestanden als read-only te behandelen, ook als ze na bewerking nog compileren.

Hoe ziet een goed "single integration point" eruit?

Houd één "hook"-bestand dat je custom package importeert en daarin alles registreert: middleware, extra routes en kleine wiring. Als je vijf verschillende routingbestanden of meerdere gegenereerde handlers moet aanraken, glijd je richting een fork die moeilijk te upgraden is.

Hoe voeg ik custom middleware toe zonder upgrades te breken?

Schrijf middleware in je eigen package en houd het klein: request IDs, audit logging, rate limits of speciale headers. Registreer de middleware vervolgens één keer op het moment dat de router of HTTP-stack wordt aangemaakt, niet per route binnen gegenereerde handlers. Een korte httptest-controle op één verwacht header of statuscode is vaak voldoende om regressies na een re-export op te merken.

Hoe voeg ik een nieuw endpoint toe zonder het gegenereerde backend te forken?

Definieer eerst het contract van het endpoint, implementeer de handler in je custom package en registreer de route op hetzelfde integratiepunt als je middleware. Houd de handler simpel: valideer input, gebruik bestaande services, geef consistente foutvormen terug en vermijd het kopiëren van gegenereerde handlerlogica. Zo blijft je wijziging draagbaar naar een verse export.

Waarom veranderen routes en middleware-volgorde na een re-export?

De generator kan de volgorde van route-registratie, groepering of middleware-ketens veranderen. Bescherm jezelf door te vertrouwen op een stabiel registratie-seam en documenteer de middleware-volgorde direct naast de registratie (bijv. auth vóór audit). Als volgorde belangrijk is, leg dat vast en verifieer het met een kleine test.

Hoe voorkom ik het dupliceren van logica tussen het no-code model en custom Go-code?

Als je dezelfde regel zowel in het no-code model als in custom Go implementeert, zullen ze uit elkaar groeien en verwarrend gedrag geven. Zet businessregels die niet-ontwikkelaars moeten aanpassen (velden, validatie, workflows, permissies) in het no-code model en hou infrastructuurzaken (logging, auth-integratie, rate limits, headers) in je custom Go-laag. De scheiding moet duidelijk zijn voor iedereen die de repo leest.

Hoe zorg ik dat mijn custom code niet afhankelijk wordt van gegenereerde interne types?

Gegenereerde DTO's en interne structs kunnen veranderen bij exports, dus isoleer die churn aan de rand. Zet binnenkomende types om naar je eigen interne structs, voer domeinlogica daar op uit en zet resultaten weer om naar de gegenereerde response-types. Als types veranderen na een re-export, update je één adapter in plaats van compilefouten over je hele custom-laag te volgen.

Wat is de beste Git-workflow voor re-exports en customizations?

Houd gegenereerde updates gescheiden van je custom werk in Git zodat je kunt zien wat er veranderde en waarom. Een praktisch verloop is: commit eerst de re-exported gegenereerde wijzigingen, en commit daarna de minimale wiring- en custom-aanpassingen. Een korte custom changelog waarin staat wat je toevoegde en waar het woont maakt de volgende upgrade veel sneller.

Hoe plan ik upgrades zodat re-exports geen dagenlange conflicten worden?

Doe eerst een dry-run re-export in een aparte branch, bouw en draai die export los van je custom-laag zodat je weet wat er veranderd is. Re-appliqueer daarna je customizations via dezelfde seams, voer een korte regressie uit en merge pas als alles groen is. Als iets niet via een seam kan, voeg dan één nieuw seam toe en gebruik die voortaan.

Wat zijn de meest gemaakte fouten die upgrades lastig maken?

Doe geen "even één keer" bewerkingen in gegenereerde bestanden. Het voelt onschuldig bij een kleine bugfix, maar over maanden weet je niet meer waarom het aangepast is en breekt een nieuwe generator-versie mogelijk je aanpassingen. Houd custom code geconcentreerd in een klein aantal duidelijke plekken en documenteer waarom een wijziging buiten je custom-package gemaakt moest worden.

Gemakkelijk te starten
Maak iets geweldigs

Experimenteer met AppMaster met gratis abonnement.
Als je er klaar voor bent, kun je het juiste abonnement kiezen.

Aan de slag