Skip to content
griban.dev
← terug_naar_blog
nodejs

Moderne REST API's bouwen met Node.js 23 en Express 5.0

Ruslan Griban8 min leestijd
delen:

Introductie

Het landschap van backend-ontwikkeling heeft de afgelopen twee jaar een tektonische verschuiving ondergaan. Bijna tien jaar lang was Express 4.x de industriestandaard, waarbij ontwikkelaars afhankelijk waren van externe libraries voor basistaken zoals het afhandelen van asynchrone fouten of het maken van HTTP-verzoeken. Met de komst van Express 5.0 en Node.js 22/23 LTS is het ecosysteem echter uitgegroeid tot een meer gestroomlijnde, performante en veilige omgeving.

Het bouwen van een REST API in 2025 gaat niet langer alleen over routing; het gaat over type-veiligheid, contract-first design en het benutten van native runtime-mogelijkheden die voorheen niet beschikbaar waren. Deze gids verkent hoe je professionele REST API's bouwt met de nieuwste functies van Node.js en Express, van de basisinstallatie tot geavanceerde architecturale patronen.

De moderne basis: Node.js 22 en Express 5.0

Voordat we een enkele regel code schrijven, is het essentieel om de moderne omgeving te begrijpen. Node.js 22 heeft functies geïntroduceerd die de "dependency-moeheid" aanzienlijk verminderen.

ES Modules (ESM) omarmen

CommonJS (require) is in feite een verouderd formaat in het moderne Node.js-ecosysteem. Door "type": "module" in je package.json in te stellen, krijg je toegang tot top-level await en betere statische analyse voor bundling-tools.

{
  "name": "modern-express-api",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "express": "^5.0.0",
    "zod": "^3.23.0"
  }
}

Express 5.0: Native Async-ondersteuning

De belangrijkste update in Express 5.0 is de native afhandeling van Promises. In Express 4 zou een niet-afgehandelde rejection in een async route het verzoek laten hangen of het proces laten crashen, tenzij het verpakt was in een try-catch blok of een helper zoals express-async-handler. Express 5.0 vangt deze fouten automatisch op en geeft ze door aan je globale error-handling middleware.

Native Fetch en WebSockets

Node.js 22 stabiliseert de native fetch API. Dit betekent dat je REST API nu kan communiceren met andere microservices zonder de overhead van axios of node-fetch. Daarnaast biedt de toevoeging van node:ws een native pad voor het toevoegen van real-time mogelijkheden aan je RESTful endpoints.

Architecturale uitmuntendheid: Het drielagenmodel

Een veelgemaakte fout in Express-ontwikkeling is het "Fat Controller"-syndroom, waarbij alle business-logica, database-queries en validatie zich binnen de route-handler bevinden. Om een schaalbare API te bouwen, implementeren we een Modulaire Drielagenarchitectuur.

1. De Controller-laag

De enige verantwoordelijkheid van de controller is het afhandelen van de HTTP-"interface". Het analyseert het verzoek, roept de juiste service aan en retourneert een geformatteerd antwoord. Het mag nooit rechtstreeks met de database communiceren.

2. De Service-laag

Dit is het hart van je applicatie. De service-laag bevat de kern van de business-logica. Als je een korting moet berekenen, een e-mail moet verzenden of de geschiktheid van een gebruiker moet controleren, gebeurt dat hier. Deze laag is framework-agnostisch, waardoor het eenvoudig is om te testen of later over te stappen naar een ander framework.

3. De Data Access Layer (DAL)

De DAL communiceert met je database. Met een ORM zoals Prisma of een ODM zoals Mongoose abstraheert deze laag de queries. Door de datatoegang te isoleren, kun je overstappen van PostgreSQL naar MongoDB met minimale impact op je business-logica.

Een diagram dat de stroom van een HTTP-verzoek door drie lagen laat zien: de Controller-laag, de Service-laag en de Data Access Layer, leidend naar een database.

CRUD implementeren met type-veiligheid en validatie

In 2025 is het vertrouwen op input van de client een kritiek beveiligingsrisico. We gebruiken Zod voor runtime-validatie en TypeScript voor veiligheid tijdens het compileren.

Het schema definiëren

Zod stelt je in staat om een schema te definiëren dat de req.body valideert en tegelijkertijd een TypeScript-type genereert.

import { z } from 'zod';
 
export const CreateUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  role: z.enum(['USER', 'ADMIN']).default('USER'),
});
 
type CreateUserDto = z.infer<typeof CreateUserSchema>;

De Express 5.0 Route Handler

Merk op hoe schoon de route wordt wanneer we gebruikmaken van de native promise-afhandeling van Express 5.0 en een gecentraliseerde validatie-middleware.

import express from 'express';
import { userService } from '../services/user.service.js';
import { validate } from '../middleware/validate.js';
import { CreateUserSchema } from '../schemas/user.schema.js';
 
const router = express.Router();
 
router.post('/', validate(CreateUserSchema), async (req, res) => {
  // Logica wordt alleen uitgevoerd als de validatie slaagt
  const newUser = await userService.createUser(req.body);
  res.status(201).json(newUser);
});
 
export default router;

Gecentraliseerde foutafhandeling

In plaats van verspreide res.status(500) aanroepen, gebruiken we een globale error handler. Express 5.0 maakt dit krachtiger door fouten die uit async-functies worden gegooid automatisch op te vangen.

// middleware/errorHandler.js
export const errorHandler = (err, req, res, next) => {
  const statusCode = err.statusCode || 500;
  const message = err.message || 'Interne Serverfout';
 
  console.error(`[Fout] ${req.method} ${req.url}:`, err);
 
  res.status(statusCode).json({
    status: 'error',
    code: err.code || 'INTERNAL_ERROR',
    message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
};

Geavanceerde technieken: Beveiliging en prestaties

Een productie-klare API vereist meer dan alleen CRUD-operaties. Het vereist een robuuste beveiligingshouding en inzicht in de Node.js event loop.

Het Node.js permissiemodel

Een van de meest opwindende toevoegingen in Node.js 22 is het experimentele permissiemodel. Je kunt nu de toegang van je API tot de omgeving beperken. Bijvoorbeeld: node --experimental-permission --allow-fs-read=/tmp/ --allow-net=api.stripe.com server.js Dit zorgt ervoor dat zelfs als een dependency gecompromitteerd is, de aanvaller je /etc/passwd bestand niet kan lezen of gegevens naar een kwaadaardig domein kan sturen.

Zware berekeningen afhandelen

Node.js is single-threaded. Als je API grote afbeeldingen moet verwerken of complexe PDF's moet genereren, zal dit de event loop blokkeren, waardoor andere verzoeken niet kunnen worden afgehandeld.

  • Oplossing: Gebruik Worker Threads voor CPU-intensieve taken of verplaats ze naar een background worker zoals BullMQ met behulp van Redis. Dit houdt je REST API responsief.

Security Headers en opschoning (sanitization)

Gebruik altijd Helmet.js om veilige HTTP-headers in te stellen. Het beschermt standaard tegen veelvoorkomende kwetsbaarheden zoals Cross-Site Scripting (XSS) en clickjacking.

import helmet from 'helmet';
const app = express();
app.use(helmet()); // Stelt 15+ security headers in

Een conceptuele illustratie van een beveiligingsschild dat een Node.js-logo beschermt, met iconen die HTTP-headers, datavalidatie en het Node.js-permissiemodel vertegenwoordigen.

Real-world scenario: Geavanceerde filtering implementeren

Moderne API's moeten vaak complexe zoekopdrachten ondersteunen. In plaats van aangepaste logica voor elke route te schrijven, kunnen we een herbruikbare "Query Features" utility bouwen.

class APIFeatures {
  constructor(query, queryString) {
    this.query = query; // De Prisma of Mongoose query
    this.queryString = queryString; // req.query
  }
 
  filter() {
    const queryObj = { ...this.queryString };
    const excludedFields = ['page', 'sort', 'limit', 'fields'];
    excludedFields.forEach(el => delete queryObj[el]);
 
    // Geavanceerde filtering (bijv. price[gte]=500)
    let queryStr = JSON.stringify(queryObj);
    queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g, match => `$${match}`);
 
    this.query = this.query.find(JSON.parse(queryStr));
    return this;
  }
 
  paginate() {
    const page = this.queryString.page * 1 || 1;
    const limit = this.queryString.limit * 1 || 100;
    const skip = (page - 1) * limit;
    this.query = this.query.skip(skip).limit(limit);
    return this;
  }
}

Hiermee kun je verzoeken zoals GET /api/products?price[gte]=100&page=2&limit=20 afhandelen met slechts een paar regels code in je service-laag.

Essentiële tools voor 2025

Tool Doel Waarom het essentieel is
Prisma ORM Biedt volledige type-veiligheid voor je database-schema.
PM2 Procesbeheer Regelt clustering om alle CPU-cores te benutten en zorgt voor zero-downtime herstarts.
Swagger UI Documentatie Genereert automatisch interactieve API-docs vanuit je OpenAPI 3.1 specificatie.
Winston Logging Gestructureerde JSON-logging is vereist voor moderne observability tools zoals Datadog.

Veelgestelde vragen

Hoe bouw ik vanaf nul een RESTful API met Node.js en Express?

Om vanaf nul een API te bouwen, initialiseer je een Node.js-project met npm init, installeer je Express en maak je een entry point-bestand aan. Vervolgens definieer je routes met app.get(), app.post(), enz., en gebruik je middleware om JSON te parsen en fouten af te handelen.

Wat is het verschil tussen Node.js en Express.js bij API-ontwikkeling?

Node.js is de JavaScript runtime-omgeving waarmee je code op de server kunt draaien, terwijl Express.js een minimaal webframework is dat bovenop Node.js is gebouwd. Node.js biedt de kernmogelijkheden voor netwerken, terwijl Express routing, middleware-integratie en verzoekafhandeling vereenvoudigt.

Hoe ga ik om met authenticatie en autorisatie in een Node.js REST API?

Authenticatie wordt meestal afgehandeld met JSON Web Tokens (JWT) of sessie-cookies via middleware zoals Passport.js of aangepaste logica. Autorisatie wordt geïmplementeerd door de rol of permissies van de gebruiker (geëxtraheerd uit het token) te controleren tegen de vereisten van de specifieke route.

Wat zijn de best practices voor het structureren van een Node.js Express-project?

Een best-practice structuur maakt gebruik van een gelaagde architectuur, waarbij code wordt gescheiden in mappen voor Controllers, Services, Models (DAL) en Middleware. Deze scheiding van belangen zorgt ervoor dat de business-logica geïsoleerd is van de HTTP-transportlaag, waardoor de codebase gemakkelijker te testen en te onderhouden is.

Hoe verbind je een Node.js REST API met een MongoDB-database?

Je maakt verbinding met MongoDB met de officiële MongoDB-driver of een ODM zoals Mongoose. Je stelt een verbindingsstring in je omgevingsvariabelen in en gebruikt een singleton-patroon om ervoor te zorgen dat de databaseverbinding wordt gedeeld over de service-laag van je applicatie.

Conclusie

Het bouwen van REST API's met Node.js en Express is uitgegroeid tot een geavanceerde discipline. De release van Express 5.0 markeert een keerpunt waar asynchrone patronen eindelijk volwaardig worden ondersteund, wat boilerplate en foutgevoelige code aanzienlijk vermindert. Door dit te combineren met de native functies van Node.js 22 — zoals het permissiemodel en de fetch API — en een strikte drielagenarchitectuur, kunnen ontwikkelaars systemen bouwen die niet alleen performant zijn, maar ook veerkrachtig en veilig.

Geef bij het verder ontwikkelen prioriteit aan contract-first design met OpenAPI en runtime-validatie met Zod. Deze tools zorgen ervoor dat naarmate je API groeit, deze een betrouwbaar contract blijft voor je frontend- en mobiele consumenten. De "Express-manier" in 2025 draait om meer doen met minder: minder dependencies, meer native functies en schonere, type-veilige code.

rocket_launch

Ready to start your project?

Let's discuss how I can help bring your ideas to life with modern web technologies and AI.

Get in Touch