![]()
Serveur web de référence avec TypeScript et Koa
L'envie de construire from scratch, sans les boîtes noires des gros frameworks. Un serveur HTTP épuré, avec deux outils que j'adore : TypeScript et Koa. C'est parti ! 🚀
Pourquoi cette stack ?
TypeScript: Le garde-fou
Pas un "plus", c'est fondamental. JavaScript devient robuste et sûr.
- Typage statique : détecte les erreurs avant l'exécution. Un contrôle pré-vol.
- Lisibilité : code structuré, facile à reprendre des mois plus tard.
- IDEs boostés : autocomplétion, erreurs en temps réel. Un copilote permanent.
Koa: Minimaliste mais costaud
Créé par l'équipe d'Express, mais volontairement simple.
- Design épuré : logique facile à suivre, structure claire.
- JavaScript moderne :
async/awaitnatif. Fini le callback hell. - Apprentissage forcé : peu de fonctionnalités embarquées = vous comprenez vraiment ce qui se passe.
Prêt à construire ? 💪
Démarrage
Node.js et npm requis.
-
Initialisation :
npm init -ycrée lepackage.json. -
Installation :
# TypeScript et exécutionnpm install --save typescript ts-node# Serveur webnpm install --save koa @types/koa koa-router @types/koa-routerLes packages
@types/permettent à TypeScript de comprendre les libs JavaScript.
TypeScript + Node.js
Node.js ne parle pas TypeScript nativement. ts-node fait le pont : transpile et exécute en une seule opération.
Test rapide, créez src/server.ts :
console.log('Hello world');
Ensuite, configurons un script de démarrage dans notre package.json :
{"name": "the-app-name","version": "1.0.0","description": "","main": "src/server.ts","scripts": {"start": "ts-node src/server.ts"},"author": "","license": "ISC","dependencies": {"@types/koa": "^2.11.6","@types/koa-router": "^7.4.1","koa": "^2.13.0","koa-router": "^10.0.0","ts-node": "^9.0.0","typescript": "^4.0.5"}}
npm start → "Hello World" = ça marche ! 🎉
N'oubliez pas le .gitignore :
# Dépendances/node_modules# Logsnpm-debug.log*yarn-debug.log*yarn-error.log*# Divers.DS_Store.env*
Gérer les requêtes
La partie fun : Koa gère le trafic, route les requêtes, renvoie les réponses.
Serveur basique répondant sur / :
import Koa, { Middleware } from 'koa';import Router from 'koa-router';const PORT = 8080;const app = new Koa();const router = new Router();// Voici la logique de notre routeconst helloWorldController: Middleware = async (ctx) => {console.log('Une requête est arrivée !');ctx.body = {message: 'Hello World!',};};router.get('/', helloWorldController);// On dit à notre app d'utiliser le routerapp.use(router.routes()).use(router.allowedMethods());// Et enfin, on démarre le serveurapp.listen(PORT, () => {console.log(`🚀 Le serveur tourne sur le port ${PORT}`);});
Point clé : Koa est minimaliste. Routage, body parsing... tout s'importe séparément. Contrôle total.
Les middlewares
app.use() permet d'enchaîner des fonctions. Une requête traverse chaque middleware, qui peut inspecter ou modifier le contexte (ctx) avant de passer au suivant.
// Un middleware simple qui ajoute de l'argent au contextefunction addMoneyMiddleware(ctx, next) {ctx.money = (ctx.money || 0) + 1;return next(); // C'est crucial ! Ça passe le contrôle au middleware suivant.}// L'utiliser pour TOUTES les routesapp.use(addMoneyMiddleware); // ctx.money vaut maintenant 1app.use(addMoneyMiddleware); // ctx.money vaut maintenant 2// L'utiliser seulement pour un groupe de routes spécifiquerouter.use('/rich', addMoneyMiddleware) // ctx.money vaut maintenant 3 pour cette route.get('/rich', (ctx) => {ctx.body = `Vous avez ${ctx.money} euros.`; // Retourne "Vous avez 3 euros."});router.get('/not-rich', (ctx) => {ctx.body = `Vous avez ${ctx.money} euros.`; // Retourne "Vous avez 2 euros."});
Pattern puissant pour séparer auth, logging, etc.
L'objet context
ctx regroupe request et response de Node. API élégante :
import Koa from 'koa';const app = new Koa();app.use(async (ctx) => {// Accéder aux données de la requêteconsole.log(ctx.request.url); // L'URL demandéeconsole.log(ctx.request.query); // La query string parséeconsole.log(ctx.request.body); // Nécessite un middleware body-parser// Définir la réponsectx.body = 'Hello, World!'; // Le corps de la réponsectx.status = 200; // Le code de statut HTTPctx.type = 'text/plain'; // L'en-tête Content-Type// Partager des données entre middlewaresctx.state.user = { id: 1, name: 'John Doe' };});app.listen(3000);
ctx = centre de commandement de la requête.
Structure d'une vraie app
L'architecture en couches garde le code maintenable et testable :
- Router : endpoints de l'API.
- Controller : logique de chaque route.
- Service : logique métier, accès BDD.
- Model : structure des données.
Exemple :
// --- router.ts ---import Router from 'koa-router';import { getUsers, createUser } from './controllers/userController';const router = new Router();router.get('/users', getUsers);router.post('/users', createUser);export default router;// --- controllers/userController.ts ---import { Context } from 'koa';import * as userService from '../services/userService';export const getUsers = async (ctx: Context) => {ctx.body = await userService.getAllUsers();};export const createUser = async (ctx: Context) => {// Suppose qu'un middleware body parser est utiliséconst userData = ctx.request.body;ctx.status = 201; // Createdctx.body = await userService.createUser(userData);};// --- services/userService.ts ---import { User } from '../models/User';export const getAllUsers = async () => {// Imaginons que c'est un appel à la base de donnéesreturn User.findAll();};export const createUser = async (userData: any) => {// Imaginons que cela sauvegarde en base de donnéesreturn User.create(userData);};
Chaque partie fait une seule chose.
Gestion d'erreurs et logging
Indispensable en production. Le pattern middleware rend ça élégant :
import Koa from 'koa';import logger from 'koa-logger';const app = new Koa();// Mon middleware générique de gestion d'erreurs. Je le place tout en haut.app.use(async (ctx, next) => {try {await next();} catch (err) {ctx.status = err.status || 500;ctx.body = {message: err.message,// Je n'affiche la stack qu'en développementstack: process.env.NODE_ENV === 'development' ? err.stack : undefined,};// Logger aussi l'erreur dans la consolectx.app.emit('error', err, ctx);}});// Middleware de logging pour les requêtesapp.use(logger());// Écouteur central d'erreursapp.on('error', (err, ctx) => {console.error('Erreur Serveur:', err.message, { url: ctx.url });});// Vos routes et autres middlewares iraient ici...app.listen(3000);
Aucune erreur ne passe, logs clairs.
En résumé
D'un dossier vide à un serveur fonctionnel. TypeScript + Koa = base solide. C'est un point de départ, le vrai plaisir commence quand vous développez vos propres idées.
Bon code ! 🚀