Pourquoi CI/CD pour Express avec GitHub Actions ?
Le Continuous Integration et Continuous Deployment (CI/CD) sont des pratiques essentielles en développement logiciel modernes. Ils permettent aux développeurs de vérifier automatiquement leur code, de détecter les erreurs en temps réel et de déployer rapidement leurs applications. Pour un projet Express, GitHub Actions est une solution idéale car il offre une intégration fluide avec le processus de développement et offre des outils puissants pour automatiser le CI/CD.
Un cas concret d'utilisation serait le déploiement d'une application API qui gère les données utilisateurs. Avec un flux de travail CI/CD, chaque fois qu'un développeur soumet une modification via GitHub, le code est automatiquement testé et déployé sur un serveur. Cela permet une meilleure collaboration entre les équipes, réduit les erreurs humaines et accélère le lancement des nouvelles fonctionnalités.
Prerequis
Pour suivre ce tutoriel, vous aurez besoin des connaissances suivantes :
- Connaissance de base d'Express
- Un compte GitHub avec une organisation ou un dépôt privé
- Node.js et npm installés sur votre machine (v14.0.0 ou plus)
- Git installé sur votre machine
Concepts fondamentaux
Workflow GitHub Actions
Un workflow dans GitHub Actions est une séquence d'étapes qui s'exécutent automatiquement à chaque événement déclenchant le workflow. Pour un projet Express, un workflow typique pourrait inclure les étapes suivantes :
- Checkout du dépôt : Récupérer le code source du dépôt GitHub.
- Installation des dépendances : Installer toutes les dépendances nécessaires à l'application.
- Tests unitaires : Exécuter les tests unitaires pour s'assurer que le code fonctionne correctement.
- Déploiement : Déployer l'application sur un serveur.
Configuration du Workflow
Le fichier de configuration d'un workflow est écrit en YAML et se trouve dans le répertoire .github/workflows de votre dépôt GitHub. Voici un exemple simple d'un workflow pour un projet Express :
## .github/workflows/main.yml
name: CI/CD for Express App
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
Bloc de Code : Installation des Dépendances
Voici un exemple de fichier package.json pour un projet Express simple :
{
"name": "express-app",
"version": "1.0.0",
"description": "A simple Express app",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "jest"
},
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"jest": "^26.6.3"
}
}
Bloc de Code : Tests Unitaires
Voici un exemple de fichier test/index.test.js pour les tests unitaires :
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
// Test unitaire
const request = require('supertest');
describe('GET /', () => {
it('should return "Hello World!"', async () => {
const response = await request(app).get('/');
expect(response.status).toBe(200);
expect(response.text).toBe('Hello World!');
});
});
Mise en pratique : projet fil rouge
Mini-Projet : Gestionnaire de Tâches
Dans ce mini-projet, nous allons créer un simple gestionnaire de tâches avec Express. Le projet comprendra les routes suivantes :
- GET /tasks : Obtenir la liste des tâches
- POST /tasks : Ajouter une nouvelle tâche
- PUT /tasks/:id : Mettre à jour une tâche existante
- DELETE /tasks/:id : Supprimer une tâche
Étapes pour le Projet
Initialisation du projet
mkdir express-task-manager cd express-task-manager npm init -yInstallation des dépendances
npm install express body-parserStructure de fichiers
express-task-manager/ ├── .github/ │ └── workflows/ │ └── main.yml ├── index.js ├── package.json └── tasks.jsFichier
index.jsconst express = require('express'); const bodyParser = require('body-parser'); const app = express(); const port = 3000; const tasks = require('./tasks'); app.use(bodyParser.json()); // Récupérer la liste des tâches app.get('/tasks', (req, res) => { res.send(tasks); }); // Ajouter une nouvelle tâche app.post('/tasks', (req, res) => { const newTask = req.body; tasks.push(newTask); res.status(201).send(newTask); }); // Mettre à jour une tâche existante app.put('/tasks/:id', (req, res) => { const id = parseInt(req.params.id); const updatedTask = req.body; tasks[id] = { ...tasks[id], ...updatedTask }; res.send(tasks[id]); }); // Supprimer une tâche app.delete('/tasks/:id', (req, res) => { const id = parseInt(req.params.id); tasks.splice(id, 1); res.status(204).send(); }); app.listen(port, () => { console.log(`Server running at http://localhost:${port}`); });Fichier
tasks.jslet tasks = []; module.exports = tasks;Configuration du Workflow GitHub Actions (
main.yml)# .github/workflows/main.yml name: CI/CD for Express App on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build-and-test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Set up Node.js uses: actions/setup-node@v2 with: node-version: '14' - name: Install dependencies run: npm install - name: Run tests run: npm testConfiguration des Tests Unitaires (
tasks.test.js)const request = require('supertest'); const app = require('../index'); describe('GET /tasks', () => { it('should return an array of tasks', async () => { await request(app).post('/tasks').send({ title: 'Task 1' }); const response = await request(app).get('/tasks'); expect(response.status).toBe(200); expect(Array.isArray(response.body)).toBe(true); }); }); describe('POST /tasks', () => { it('should add a new task', async () => { const response = await request(app).post('/tasks').send({ title: 'Task 1' }); expect(response.status).toBe(201); expect(response.body.title).toBe('Task 1'); }); }); describe('PUT /tasks/:id', () => { it('should update an existing task', async () => { await request(app).post('/tasks').send({ title: 'Task 1' }); const taskId = 0; const response = await request(app).put(`/tasks/${taskId}`).send({ title: 'Updated Task 1' }); expect(response.status).toBe(200); expect(response.body.title).toBe('Updated Task 1'); }); }); describe('DELETE /tasks/:id', () => { it('should delete an existing task', async () => { await request(app).post('/tasks').send({ title: 'Task 1' }); const taskId = 0; const response = await request(app).delete(`/tasks/${taskId}`); expect(response.status).toBe(204); }); });
Erreurs frequentes et debugging
Erreur : "Cannot find module 'express'"
// ❌ Mauvais
const express = require('express');
javascript
// ✅ Correct
npm install express --save
const express = require('express');
Erreur : "SyntaxError: Unexpected token import"
Si vous obtenez cette erreur, assurez-vous que vous utilisez un environnement qui supporte ES6 modules. Vous pouvez ajouter le type module dans votre package.json :
{
"type": "module"
}
Erreur : "Error: listen EADDRINUSE: address already in use"
Si vous obtenez cette erreur, il peut y avoir un autre processus qui utilise déjà le port sur lequel l'application s'exécute. Vous pouvez changer le port dans votre fichier index.js :
const port = 3001; // Changer de port
Pour aller plus loin
Piste 1 : Ajouter des Tests d'Intégration
Vous pouvez ajouter des tests d'intégration pour vous assurer que toutes les parties de votre application fonctionnent ensemble correctement.
// tasks.integration.test.js
const request = require('supertest');
const app = require('../index');
describe('Integration tests', () => {
it('should add a task and retrieve it', async () => {
const response1 = await request(app).post('/tasks').send({ title: 'Task 1' });
expect(response1.status).toBe(201);
const taskId = 0;
const response2 = await request(app).get(`/tasks/${taskId}`);
expect(response2.status).toBe(200);
expect(response2.body.title).toBe('Task 1');
});
});
Piste 2 : Déploiement sur Heroku
Vous pouvez déployer votre application Express sur Heroku en utilisant GitHub Actions.
## .github/workflows/deploy.yml
name: Deploy to Heroku
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm install
- name: Build the app (if necessary)
run: npm run build # Si votre application nécessite une étape de construction
- name: Deploy to Heroku
uses: akhileshns/heroku-deploy@v3.12.12
with:
heroku_api_key: $secrets.HEROKU_API_KEY
heroku_app_name: "your-heroku-app-name"
heroku_email: "your-email@example.com"
Piste 3 : Utiliser Docker
Vous pouvez utiliser Docker pour encapsuler votre application Express et la déployer facilement sur un serveur.
## Dockerfile
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
Créez ensuite un fichier docker-compose.yml :
## docker-compose.yml
version: '3'
services:
app:
build: .
ports:
- "3000:3000"
Déployez l'application avec Docker Compose :
docker-compose up -d
Défi pratique
- Ajoutez une nouvelle fonctionnalité à votre gestionnaire de tâches, par exemple, la possibilité de filtrer les tâches par statut (complétées/non-complétées).
- Configurez un workflow GitHub Actions pour déployer votre application sur AWS Elastic Beanstalk.
- Créez une documentation complète pour votre API en utilisant Swagger.
En suivant ces étapes et en résolvant les erreurs communes, vous serez bien équipé pour mettre en œuvre un CI/CD efficace pour vos projets Express avec GitHub Actions.