Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

Parcours Node.js / TypeScript

Confirmé 40 min prérequis : axes 5-7 + 8.0 lus

🎯 Objectif : construire des APIs Node.js modernes en TypeScript strict — choisir le bon framework selon le contexte, structurer un projet, valider, authentifier, tester.

À l'issue de cet axe, tu sauras :

  • Choisir entre Hono, Fastify, Express 5, NestJS selon le projet
  • Construire une API REST typée avec validation Zod et middleware
  • Authentifier avec JWT en cookie HttpOnly
  • Connecter une base de données via Drizzle ou Prisma 7
  • Tester avec Vitest + Supertest sur une DB éphémère
PourContre
Même langage que le frontend → partage de code (types, validation Zod, libs)Mono-thread (workers ou multi-process pour le CPU-bound)
Excellent pour I/O-bound (APIs, streaming, temps réel)Moins adapté aux calculs lourds (préférer Go/Rust/Python si besoin)
Écosystème npm gigantesque, Bun et Deno dispo en alternativeComplexité de l’écosystème (ESM/CJS, plein d’outils similaires)
TypeScript strict ≈ Java/C# en sécurité de typage, sans la cérémonieUn projet bâclé se transforme vite en spaghetti

Verdict 2026 : c’est le choix par défaut si ton frontend est en JS/TS, ou si tu fais du temps réel (WebSocket, SSE, edge).

StyleMinimaliste, web standards (Request/Response du Fetch API)
TypeScriptType-safe end-to-end, inférence excellente
PerformanceTrès rapide (~55 000 req/s sur Node, davantage sur Bun)
CiblesNode, Bun, Deno, Cloudflare Workers, Vercel Edge, AWS Lambda
ÉcosystèmeMiddlewares pour CORS, JWT, logger, validateur Zod, etc.
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
const app = new Hono();
app.use(logger(), cors());
app.get('/health', (c) => c.json({ status: 'ok' }));
app.get('/users/:id', (c) => {
const id = c.req.param('id');
return c.json({ id, name: 'Alice' });
});
export default app;

Pourquoi Hono est devenu le défaut en 2026 : tu écris une fois, tu déploies sur n’importe quoi (Node serveur classique ou edge worker). Plus besoin de choisir au début entre Express et Cloudflare Workers.

import express from 'express';
const app = express();
app.use(express.json());
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.get('/users/:id', async (req, res) => {
const user = await getUser(req.params.id);
res.json(user);
});
app.listen(3000);

Express 5 (sortie stable fin 2024) apporte :

  • Gestion async automatique (les exceptions dans les handlers async remontent au error middleware sans wrap manuel).
  • Suppression d’APIs deprecated.
  • Compatibilité avec Node 20+.

Quand le choisir : tu hérites d’un projet existant, ou tu veux la maturité maximum d’écosystème (énorme catalogue de middlewares).

import Fastify from 'fastify';
const app = Fastify({ logger: true });
app.get('/users/:id', {
schema: {
params: { type: 'object', properties: { id: { type: 'string' } } },
response: { 200: { type: 'object', properties: { id: { type: 'string' }, name: { type: 'string' } } } },
},
}, async (req) => {
return { id: req.params.id, name: 'Alice' };
});
await app.listen({ port: 3000 });

Forces : ~3× plus rapide qu’Express, JSON Schema natif (validation + sérialisation rapides), plugins encapsulés (système d’extension propre).

Quand le choisir : APIs Node serveur classique avec exigence de perf, équipe qui n’a pas besoin de l’edge.

import { Controller, Get, Param } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get(':id')
findOne(@Param('id') id: string) {
return { id, name: 'Alice' };
}
}

Forces : architecture imposée (modules, services, DI), décorateurs, intégrations avec presque tout (TypeORM, Prisma, GraphQL, microservices).

Faiblesses : verbeux, courbe d’apprentissage, sur-dimensionné pour les petits projets.

Quand le choisir : équipe 10+ devs, projet long-terme, équipes Java/Angular qui veulent retrouver leurs marques.

Si…Choisis
Tu démarres un nouveau projet en 2026Hono
Tu vises l’edge (Cloudflare, Vercel)Hono
Tu maintiens un Express existantExpress 5 (ne migre que si bénéfice clair)
Performance Node serveur, schema-drivenFastify
Grosse équipe, structure imposée, architecture par modulesNestJS

Pour une API moyenne, voici une structure qui passe à l’échelle sans framework imposant :

taskly-api/
├── src/
│ ├── app.ts ← composition (middlewares + routes)
│ ├── main.ts ← entry point (lance le serveur)
│ ├── config.ts ← parsing des env via Zod
│ ├── db/
│ │ ├── schema.ts ← schémas Drizzle / Prisma
│ │ └── index.ts ← client DB
│ ├── modules/
│ │ ├── auth/
│ │ │ ├── auth.routes.ts
│ │ │ ├── auth.service.ts
│ │ │ └── auth.schemas.ts
│ │ └── tasks/
│ │ ├── tasks.routes.ts
│ │ ├── tasks.service.ts
│ │ └── tasks.schemas.ts
│ ├── middleware/
│ │ ├── auth.ts
│ │ └── error.ts
│ └── lib/
│ ├── jwt.ts
│ └── logger.ts
├── tests/
│ └── ...
├── package.json
├── tsconfig.json
└── .env.example

Découpage par module métier (auth, tasks) plutôt que par couche technique (controllers/, services/). Plus facile à naviguer quand le projet grossit.

OutilStyleQuand préférer
DrizzleSQL-like, ESM, edge-compatiblePerformance, edge deployment, contrôle SQL
Prisma 7Schema-first, migrations puissantesProductivité, équipe pas SQL-experte
TypeORMDecorators (NestJS-friendly)Si déjà NestJS
KyselyQuery builder TypeScript purType-safety extrême, pas d’ORM

Prisma 7 a abandonné le moteur Rust pour un moteur TypeScript/WebAssembly. Conséquences :

  • Bundle ~85 % plus léger (1.6 MB vs 11 MB).
  • Performances ~3× meilleures sur grands résultats.
  • Compatible edge (Cloudflare Workers).
// schema.prisma
model Task {
id Int @id @default(autoincrement())
title String
done Boolean @default(false)
createdAt DateTime @default(now())
ownerId Int
owner User @relation(fields: [ownerId], references: [id])
}
// Code TS
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const tasks = await prisma.task.findMany({
where: { done: false, ownerId: userId },
orderBy: { createdAt: 'desc' },
take: 20,
});
schema.ts
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
export const tasks = sqliteTable('tasks', {
id: integer('id').primaryKey(),
title: text('title').notNull(),
done: integer('done', { mode: 'boolean' }).default(false),
ownerId: integer('owner_id').notNull(),
createdAt: text('created_at').default(sql`CURRENT_TIMESTAMP`),
});
// Code
import { db } from './db';
import { eq } from 'drizzle-orm';
const tasks = await db
.select()
.from(tasks)
.where(eq(tasks.ownerId, userId))
.orderBy(desc(tasks.createdAt))
.limit(20);

✅ Plus proche du SQL → contrôle total. ✅ Bundle minuscule, edge-ready. ❌ Moins de magie : à toi d’écrire les jointures complexes.

Choix par défaut 2026 : Drizzle pour edge ou perfs ; Prisma 7 pour productivité maximale et migrations DSL.

Fenêtre de terminal
npm install -D vitest supertest
tests/tasks.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { app } from '../src/app';
import { db } from '../src/db';
beforeEach(async () => {
await db.delete(tasks);
await db.insert(tasks).values([{ title: 'test', ownerId: 1 }]);
});
describe('GET /tasks', () => {
it('renvoie la liste', async () => {
const res = await app.request('/tasks', {
headers: { Authorization: 'Bearer test-token' },
});
expect(res.status).toBe(200);
const data = await res.json();
expect(data.length).toBe(1);
});
});

Avec Hono, app.request() te permet de tester sans démarrer un vrai serveur. Avec Express/Fastify, Supertest ou un simple fetch après listen().

Pour les tests bout-en-bout sur vraie DB : Testcontainers (lance un Postgres temporaire en Docker pour les tests). Robuste, mais lent.

RuntimeÉtat 2026Quand l’envisager
BunStable, plus rapide que Node, support npm, runtime+test+package manager unifiésProjets greenfield qui veulent la perf
DenoStable, sécurité par défaut, TS natif, npm compatProjets qui privilégient sécurité et standards
Node 24LTS, TS natif via type stripping depuis Node 24Le défaut universel, valeur sûre

Hono (et beaucoup de libs modernes) tournent sur les trois runtimes sans modification.

Tu démarres une API en 2026 qui pourrait éventuellement être déployée sur Cloudflare Workers. Framework idéal ?
Pour valider le body d'une requête POST, quelle approche en 2026 ?
Dans une API Node, tu as besoin d'envoyer un email de bienvenue à l'inscription. L'envoi prend 800 ms. Que faire ?