Jeg bruker Go for produksjonstjenester der ytelse og type-sikkerhet er avgjørende. Men Node.js er fortsatt mitt foretrukne verktøy for en hel kategori av problemer — og det er ikke fordi jeg er nostalgisk. Node.js har reelle styrker som gjør det til riktig valg for visse prosjekter.
Denne guiden handler om når jeg velger Node.js, hvordan jeg strukturerer prosjekter som auth-policy-engine og learning-analytics-engine, og hva du faktisk bør forstå om event loopen for å skrive robust backend-kode.
Når Node.js slår Go
Mitt tommelfingerregel er enkel: Node.js for fart i utvikling, Go for fart i kjøring. Men det er mer nyansert enn det.
Delt kodebase med frontend. Hvis teamet ditt bygger en React-frontend og en API, er det en enorm fordel å dele typer, validering og hjelpefunksjoner mellom klient og server. Med TypeScript på begge sider eliminerer du en hel klasse av bugs — de der frontend sender en string og backend forventer et tall.
Rask prototyping. auth-policy-engine startet som en prototype. Jeg trengte å validere en idé for policy-basert tilgangskontroll — ikke bygge en ytelseskritisk tjeneste. Node.js lot meg gå fra idé til fungerende kode på dager, ikke uker. Senere, når mønstrene var bevist, kunne jeg vurdere om det var verdt å skrive om.
Økosystem-bredde. npm har pakker for alt. For learning-analytics-engine trengte jeg parsing av ulike dataformater, statistiske beregninger, og eksport til flere formater. I Go ville jeg skrevet mye av dette selv. I Node.js var det et npm install unna.
Tooling og scripting. For CLI-verktøy, build-scripts, og dev-tooling er Node.js uslåelig. Du har tilgang til hele webøkosystemet, og de fleste utviklere på teamet kan lese og vedlikeholde koden.
Forstå event loopen — for alvor
De fleste Node.js-utviklere vet at "Node er single-threaded med en event loop." Færre forstår hva det betyr i praksis.
Event loopen har faser: timers, pending callbacks, poll, check, close callbacks. Det viktigste å forstå er at CPU-bundet arbeid blokkerer alt. Hvis du parser en 50 MB JSON-fil synkront, svarer serveren din ikke på noen forespørsler i mellomtiden.
For auth-policy-engine var dette en reell bekymring. Policy-evaluering kan innebære komplekse regler med mange betingelser. Løsningen var å holde individuelle evalueringer raske (under 1ms) og bruke streams for batch-operasjoner.
Express middleware-mønster
auth-policy-engine eksponerer et Express middleware-mønster som gjør det enkelt å integrere policy-sjekker i enhver Express-app:
import { PolicyEngine, type PolicyContext } from 'auth-policy-engine';
const engine = new PolicyEngine({ policies: './policies' });
function requirePolicy(policyName: string) {
return async (req: Request, res: Response, next: NextFunction) => {
const context: PolicyContext = {
user: req.user,
resource: req.params.resourceId,
action: req.method.toLowerCase(),
environment: {
ip: req.ip,
timestamp: Date.now(),
},
};
const result = await engine.evaluate(policyName, context);
if (!result.allowed) {
res.status(403).json({
error: 'POLICY_DENIED',
reason: result.reason,
});
return;
}
req.policyResult = result;
next();
};
}
app.get('/api/courses/:id', requirePolicy('course-access'), getCourseHandler);
app.delete('/api/courses/:id', requirePolicy('course-admin'), deleteCourseHandler);Mønsteret er bevisst enkelt. Middleware-funksjonen tar et policy-navn, bygger en kontekst fra forespørselen, evaluerer policyen, og enten blokkerer eller slipper gjennom. Resultatet legges på request-objektet slik at handleren kan bruke det — for eksempel for å filtrere felter basert på brukerens tilgangsnivå.
Streams for store datasett
learning-analytics-engine prosesserer læringsdata — brukerinteraksjoner, quiz-resultater, tidsbruk per modul. For små datasett er det fristende å laste alt inn i minnet. For store datasett er streams den riktige tilnærmingen:
import { createReadStream } from 'node:fs';
import { pipeline, Transform } from 'node:stream';
import { promisify } from 'node:util';
const pipe = promisify(pipeline);
const analyticsTransform = new Transform({
objectMode: true,
transform(event, _encoding, callback) {
if (event.type === 'quiz_completed') {
const analytics = {
userId: event.userId,
moduleId: event.moduleId,
score: event.score / event.maxScore,
duration: event.endTime - event.startTime,
timestamp: new Date(event.endTime),
};
this.push(analytics);
}
callback();
},
});
await pipe(
createReadStream('events.jsonl'),
parseJSONLines(),
analyticsTransform,
writeToDatabase(),
);Streams prosesserer data chunk for chunk uten å laste hele filen inn i minnet. For learning-analytics-engine betyr dette at vi kan prosessere flere gigabyte med event-data på en maskin med 512 MB RAM. Backpressure håndteres automatisk — hvis databasen er treg, sakker hele pipelinen ned i stedet for å fylle opp minnet.
Arkitektur-prinsipper
Etter å ha bygget og vedlikeholdt Node.js-prosjekter over flere år, har jeg landet på noen prinsipper:
Bruk TypeScript med strenge innstillinger. strict: true i tsconfig er ikke valgfritt. Det fanger bugs som ville vært runtime-feil i produksjon. Ja, det er mer arbeid å skrive typene — men det er mindre arbeid enn å debugge produksjonsfeil klokken to om natten.
Hold avhengigheter minimale. Hver npm-pakke er et vedlikeholdsansvar. For auth-policy-engine har vi under 10 runtime-avhengigheter. Hvert tillegg ble vurdert opp mot å skrive det selv. For enkel funksjonalitet er egne implementasjoner ofte tryggere og enklere å vedlikeholde.
Strukturer etter domene, ikke etter type. I stedet for controllers/, services/, models/ bruker vi policy/, evaluation/, audit/. Hver mappe inneholder alt som tilhører det domenet. Det gjør det lettere å forstå grensene i systemet.
Når du IKKE bør bruke Node.js
CPU-intensive oppgaver. Bildemanipulering, kryptering av store datamengder, eller komplekse beregninger. Node.js kan gjøre det med worker threads, men det er som å bruke en skrutrekker som hammer — det fungerer, men det finnes bedre verktøy.
Systemprogrammering. Hvis du trenger presis minnestyring, direkte tilgang til OS-primitiver, eller bygger en database — bruk Go, Rust, eller C.
Når typesikkerhet er kritisk og du ikke vil bruke TypeScript. Vanilla JavaScript i backend er en oppskrift på katastrofe for alt utover prototyper. Hvis teamet ditt ikke vil bruke TypeScript, velg et språk med innebygd typesikkerhet.
Oppsummering
Node.js er ikke et andrevalg bak Go — det er et annet valg for andre problemer. For auth-policy-engine og learning-analytics-engine var det riktig verktøy: raskt å utvikle, lett å forstå for et team som allerede kan JavaScript, og kraftig nok for oppgavene vi løste.
Velg Node.js når utviklerhastighet og økosystem-tilgang trumfer rå ytelse. Velg Go når du trenger forutsigbar ytelse og enkel deployment. Og vær ærlig med deg selv om hvilken kategori prosjektet ditt faktisk faller i.