E-NO
Laboratoire local Express API 9 min de lecture

Configuration d'un laboratoire local d'API Express avec exemples pratiques : guide d'implémentation pratique

calendar_today Publié : 2026-07-23
update Dernière mise à jour : 2026-07-23
analytics Efficacité SEO : 100%
Illustration du guide technique pour « Configuration d'un laboratoire local d'API Express avec exemples pratiques : guide d'implémentation pratique ».

Introduction

Un laboratoire local sûr permet d'essayer des idées, de déboguer des problèmes et d'apprendre rapidement sans risquer la production. Dans ce guide, vous allez :

  • Monter une API Express minimale sur localhost
  • Ajouter des réglages de sécurité par défaut (helmet, CORS, limitation de taux)
  • Ajouter une journalisation structurée avec identifiants de requête
  • Écrire quelques tests avec Jest + Supertest
  • Exercer l'API avec curl et un test de charge basique
  • Connecter optionnellement un MongoDB local

Le résultat est un flux de travail répétable et inspectable que vous pouvez étendre — par exemple, en ajoutant l'authentification, une file de messages ou des tâches en arrière-plan — au fur et à mesure que vos besoins grandissent.

Aperçu du flux de travail

Suivez cette séquence et vérifiez après chaque étape.

Préparer la chaîne d'outils

  • Installez Node.js 18+ et Git. Optionnel : Docker pour un MongoDB local.
  • Créez un répertoire frais pour le laboratoire :
mkdir express-lab && cd express-lab
npm init -y

Installer les dépendances

npm install express helmet cors express-rate-limit pino pino-http zod dotenv
npm install --save-dev nodemon jest supertest cross-env

Structure du projet

express-lab/
 .env                 # variables d'environnement (ne jamais commiter de secrets)
 package.json
 src/
   index.js           # point d'entrée de l'application
   routes.js          # routes d'exemple
   errors.js          # helpers d'erreur
   db.js              # connexion MongoDB optionnelle
   note.model.js      # modèle Mongoose optionnel
 test/
   app.test.js        # tests de base

Variables d'environnement

Créez un fichier .env :

PORT=4000
HOST=127.0.0.1
CORS_ORIGIN=http://localhost:3000
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX=60
NODE_ENV=development
LOG_LEVEL=info

Scripts dans package.json

{
  "name": "express-lab",
  "version": "1.0.0",
  "type": "commonjs",
  "scripts": {
    "dev": "nodemon src/index.js",
    "start": "node src/index.js",
    "test": "cross-env NODE_ENV=test jest --runInBand --detectOpenHandles"
  },
  "jest": {
    "testEnvironment": "node",
    "verbose": false
  }
}

Code de l'application : sécurité, journalisation et points de terminaison

src/errors.js

class HttpError extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
  }
}

function notFound(req, res, next) {
  next(new HttpError(404, 'Not found'));
}

function errorHandler(err, req, res, next) { // eslint-disable-line no-unused-vars
  const status = err.status || 500;
  res.status(status).json({
    error: {
      message: err.message || 'Internal server error',
      status
    }
  });
}

module.exports = { HttpError, notFound, errorHandler };

src/routes.js

const express = require('express');
const { z } = require('zod');
const { HttpError } = require('./errors');

const router = express.Router();

router.get('/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime(), ts: Date.now() });
});

router.post('/echo', (req, res, next) => {
  const schema = z.object({ message: z.string().min(1) });
  const parsed = schema.safeParse(req.body);
  if (!parsed.success) return next(new HttpError(400, 'Invalid body'));
  res.json({ echoed: parsed.data.message });
});

router.get('/math/sum', (req, res, next) => {
  const raw = (req.query.numbers || '').toString();
  if (!raw) return next(new HttpError(400, 'Provide numbers query param'));
  const nums = raw.split(',').map(x => Number(x.trim()));
  if (nums.some(n => Number.isNaN(n))) return next(new HttpError(400, 'numbers must be comma-separated numerics'));
  const sum = nums.reduce((a, b) => a + b, 0);
  res.json({ sum, count: nums.length });
});

module.exports = router;

src/index.js

require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const pino = require('pino');
const pinoHttp = require('pino-http');
const routes = require('./routes');
const { notFound, errorHandler } = require('./errors');

const app = express();
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });

app.disable('x-powered-by');
app.set('trust proxy', false);

app.use(pinoHttp({
  logger,
  genReqId: (req) => req.headers['x-request-id'] || `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
}));
app.use(helmet());
app.use(cors({ origin: process.env.CORS_ORIGIN || 'http://localhost:3000', credentials: true }));
app.use(express.json({ limit: '100kb' }));

const limiter = rateLimit({
  windowMs: Number(process.env.RATE_LIMIT_WINDOW_MS || 60000),
  max: Number(process.env.RATE_LIMIT_MAX || 60),
  standardHeaders: true,
  legacyHeaders: false
});
app.use(limiter);

app.use('/api', routes);
app.use(notFound);
app.use(errorHandler);

const PORT = Number(process.env.PORT || 4000);
const HOST = process.env.HOST || '127.0.0.1';

app.listen(PORT, HOST, () => {
  logger.info({ PORT, HOST }, 'Server started');
});

Lancer le laboratoire

npm run dev
# Dans un autre terminal
curl -s http://127.0.0.1:4000/api/health | jq .
curl -s -X POST http://127.0.0.1:4000/api/echo -H 'Content-Type: application/json' -d '{"message":"hello"}' | jq .
curl -s 'http://127.0.0.1:4000/api/math/sum?numbers=1,2,3,4' | jq .

Tests de base

test/app.test.js

const request = require('supertest');
const express = require('express');
const routes = require('../src/routes');
const { notFound, errorHandler } = require('../src/errors');

function createTestApp() {
  const app = express();
  app.use(express.json());
  app.use('/api', routes);
  app.use(notFound);
  app.use(errorHandler);
  return app;
}

describe('Express lab', () => {
  const app = createTestApp();

  test('GET /health returns ok', async () => {
    const res = await request(app).get('/api/health');
    expect(res.statusCode).toBe(200);
    expect(res.body.status).toBe('ok');
  });

  test('POST /echo requires message', async () => {
    const res = await request(app).post('/api/echo').send({});
    expect(res.statusCode).toBe(400);
  });

  test('GET /math/sum sums numbers', async () => {
    const res = await request(app).get('/api/math/sum').query({ numbers: '5,7,8' });
    expect(res.statusCode).toBe(200);
    expect(res.body.sum).toBe(20);
  });
});

Exécutez les tests :

npm test

Vérifications rapides de charge et de sécurité

  • Limitation de taux : déclenchez-la pour confirmer le comportement 429.
for i in $(seq 1 70); do curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:4000/api/health; done | sort | uniq -c

Vous devriez voir 60 réponses avec 200 et 10 avec 429.

  • Identifiant de requête : observez les logs qui incluent un identifiant de requête. Ajoutez -H 'x-request-id: demo-123' et confirmez que le même identifiant apparaît dans les logs.
curl -H 'x-request-id: demo-123' -s http://127.0.0.1:4000/api/health > /dev/null
# Vérifiez les logs du serveur pour demo-123

Optionnel : ajouter un MongoDB local

Si vous souhaitez une couche de données réaliste, lancez MongoDB localement. Une option simple est d'exécuter un conteneur sur localhost.

# Démarrer MongoDB localement (réglages de développement par défaut)
docker run -d --name mongo-lab -p 27017:27017 mongo:6

Installez Mongoose et ajoutez une petite fonctionnalité de notes :

npm install mongoose

src/db.js

const mongoose = require('mongoose');

async function connect(uri) {
  mongoose.set('strictQuery', true);
  await mongoose.connect(uri);
}

module.exports = { connect };

src/note.model.js

const mongoose = require('mongoose');

const NoteSchema = new mongoose.Schema({
  title: { type: String, required: true },
  body: { type: String, required: true }
}, { timestamps: true });

module.exports = mongoose.model('Note', NoteSchema);

Étendez src/routes.js avec des points de terminaison CRUD (restez minimal) :

// ajouter au début
const Note = require('./note.model');

// créer une note
router.post('/notes', async (req, res, next) => {
  try {
    const schema = z.object({ title: z.string().min(1), body: z.string().min(1) });
    const parsed = schema.safeParse(req.body);
    if (!parsed.success) return next(new HttpError(400, 'Invalid body'));
    const note = await Note.create(parsed.data);
    res.status(201).json({ id: note._id.toString() });
  } catch (e) { next(e); }
});

// lister les notes
router.get('/notes', async (req, res, next) => {
  try {
    const notes = await Note.find().sort({ createdAt: -1 }).limit(10).lean();
    res.json({ notes });
  } catch (e) { next(e); }
});

Connectez la base de données dans src/index.js (ajoutez près du début) :

const { connect } = require('./db');

Et remplacez le bloc app.listen par :

async function start() {
  const mongoUri = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/express_lab';
  if (process.env.NODE_ENV !== 'test') {
    await connect(mongoUri);
  }
  app.listen(PORT, HOST, () => {
    logger.info({ PORT, HOST }, 'Server started');
  });
}

start().catch(err => {
  logger.error({ err }, 'Failed to start');
  process.exit(1);
});

Testez l'API des notes :

curl -s -X POST http://127.0.0.1:4000/api/notes \
  -H 'Content-Type: application/json' \
  -d '{"title":"first","body":"hello"}' | jq .

curl -s http://127.0.0.1:4000/api/notes | jq .

Astuces pour la reproductibilité

  • Gardez un fichier .env.example dans le dépôt pour que les collègues connaissent les variables requises.
  • Verrouillez la version de Node.js avec un .nvmrc ou l'outil de votre choix.
  • Épinglez les versions majeures des dépendances pour éviter les ruptures surprises.
  • Ajoutez un README court avec les exemples d'exécution, de test et de curl.

Dépannage

SymptômeCause probableSolution
Port déjà utiliséUn autre processus sur le PORTChangez le PORT dans .env ou tuez l'autre processus
Erreurs CORS depuis un front-end sur un port différentCORS_ORIGIN mal configuréDéfinissez CORS_ORIGIN dans .env sur l'origine du front-end
Réponses 429 sur un trafic normalLimite de taux trop stricteAugmentez RATE_LIMIT_MAX ou RATE_LIMIT_WINDOW_MS
Connexion Mongo refuséeConteneur non démarrédocker start mongo-lab et vérifiez docker ps

Plan pilote local

Gardez le premier pilote étroit et vérifiable :

  • Périmètre : /health, /echo, /math/sum uniquement ; middleware de sécurité ; journalisation ; 3 tests.
  • Chronométrage : 60 minutes de zéro à tests passants.
  • Critères de succès :
  • GET /api/health renvoie status ok en < 50 ms localement
  • POST /api/echo renvoie un message non vide
  • GET /api/math/sum renvoie la somme correcte pour 1,2,3
  • Les logs incluent un identifiant de requête, soit généré, soit provenant de l'en-tête x-request-id
  • La limitation de taux renvoie 429 quand on dépasse le max défini (60 req/min par défaut)
  • npm test passe en moins de 2 secondes sur un portable typique
  • Objectif étendu : ajouter les points de terminaison /notes avec un MongoDB local et confirmer le CRUD de base.

Conclusion

Vous disposez maintenant d'un laboratoire d'API Express compact, sécurisé et testable qui s'exécute entièrement sur localhost. La configuration est claire et répétable, vous pouvez donc expérimenter en toute sécurité, mesurer les changements et étendre les points de terminaison au fur et à mesure que vos besoins évoluent. Prochaines étapes :

  • Étendre les tests pour couvrir les cas limites et les erreurs
  • Introduire la validation de requête pour toutes les routes
  • Ajouter une base de données locale ou une file de messages selon les besoins
  • Emballer les scripts pour une configuration en une commande afin que toute l'équipe puisse exécuter le même laboratoire

Construisez incrémentalement, vérifiez chaque étape et gardez le pilote petit avant d'ajouter de la complexité.

Recherches connexes

Score de qualité de l’article

Utilité pour le lecteur 100%
  • check_circle Guide prêt à lire
  • check_circle Exemples pratiques inclus
  • check_circle URL d’article optimisée pour le SEO