Comprendre n8n : l’automatisation libre et puissante
🔹 Introduction
Aujourd’hui, les entreprises et les développeurs cherchent à automatiser les tâches répétitives, connecter des applications entre elles et fluidifier leurs workflows.
C’est là qu’entre en scène n8n, un outil d’automatisation open source qui rivalise avec des solutions comme Zapier ou Make (ex-Integromat), tout en offrant plus de liberté et de contrôle.
🧩 En résumé : n8n est une plateforme d’automatisation “fair-code” qui permet de créer des workflows visuels pour connecter des services, APIs et scripts entre eux, sans avoir à tout coder à la main.
⚙️ 1. Qu’est-ce que n8n ?
n8n (prononcé “n-eight-n”) signifie "Node for Node".
Il s’agit d’un outil d’automatisation visuelle basé sur Node.js, où tu construis des workflows sous forme de nœuds interconnectés.
Chaque nœud (node) représente :
- une action (ex : envoyer un email, créer un fichier, appeler une API)
- ou un déclencheur (trigger) (ex : un nouveau message sur Slack, un push GitLab, un fichier ajouté dans Google Drive)
🧩 Les workflows peuvent être simples ou très complexes, combinant logique conditionnelle, variables, boucles, fonctions JavaScript, etc.
🚀 2. Pourquoi utiliser n8n ?
💡 a. Open Source et auto-hébergé
Contrairement à Zapier ou Make, tu peux héberger n8n sur ton propre serveur (Docker, VPS, NAS, etc.).
Vous retrouverez ci-joint la documentation pour créer votre self-host
👉 Tu gardes le contrôle total sur tes données et aucune limite d’exécution.
Voici le résultat le résultat attendu par la construction de votre noeud 😄

🧰 b. Flexibilité extrême
n8n ne se limite pas à quelques intégrations “préfabriquées” :
- Tu peux appeler n’importe quelle API REST
- Exécuter du JavaScript personnalisé
- Gérer des conditions complexes
- Chainer des dizaines d’applications (Slack, Jira, GitLab, PostgreSQL, etc.)
🔒 c. Respect de la vie privée et du modèle “fair-code”
n8n est sous licence Fair Code, ce qui signifie que :
- Tu peux l’utiliser gratuitement pour tes projets personnels et pros
- Tu peux même le modifier ou l’intégrer à ton infrastructure interne
📊 d. Utilisable par tous
- No-code : interface graphique pour les utilisateurs non techniques
- Low-code : possibilité d’ajouter du code JS pour les développeurs
C’est une solution idéale pour les équipes mixtes (tech & métier).
🧩 3. Comment fonctionne n8n ?
Un workflow n8n est une suite de nœuds connectés :
- Trigger Node → démarre le workflow (ex: “Nouveau mail reçu”)
- Action Node → effectue une tâche (ex: “Créer un ticket Jira”)
- Condition Node → oriente la logique
- Output → résultat final (ex: “Notifier sur Slack”)
🧠 Exemple simple :
Lorsqu’un nouveau fichier est ajouté dans un dossier Google Drive → le renommer → l’envoyer par email → notifier sur Slack.
🧱 4. Installation rapide
Tu peux lancer n8n en quelques minutes grâce à Docker :
docker run -it --rm \
--name n8n \
-p 5678:5678 \
-v ~/.n8n:/home/node/.n8n \
n8nio/n8n🧩 Un petit exercice pour s’approprier n8n
Dans cet exercice, nous allons créer un workflow d’automatisation capable d’interroger périodiquement l’API GitLab afin de :
- Récupérer le dernier pipeline d’un projet,
- Extraire ses informations clés (ID, statut, auteur, commit...),
- Obtenir la liste complète des jobs associés à ce pipeline,
- Générer un résumé clair et formaté en Markdown, incluant les statuts des jobs,
- Et enfin, envoyer automatiquement ce rapport dans un canal Discord.
Le tout est complété par un mécanisme de contrôle d’erreurs robuste :
- Vérification du succès des requêtes HTTP,
- Et test de la connexion Internet avant de poursuivre, pour éviter les faux positifs.
💡 Cet exercice illustre parfaitement le concept d’un système d’alerte automatisé, comparable à ceux utilisés en supervision d’infrastructure ou dans un pipeline de déploiement applicatif.
Grâce à n8n, il devient facile de surveiller en continu l’état d’un projet et de notifier automatiquement l’équipe en cas d’anomalie.
🧭 Schéma simplifié du flux n8n
- Voici la représentation du flux logique de notre workflow dans n8n :
┌────────────────────┐
│ 1️⃣ Schedule Trigger│
└────────┬───────────┘
│
▼
┌────────────────────────────┐
│ 2️⃣ HTTP Request │
│ (GET pipeline latest) │
└────────┬───────────────────┘
│
▼
┌────────────────────────────┐
│ 3️⃣ Check HTTP Error (If) │
└────────┬───────────┬────────┘
│ │
│❌ Erreur │✅ Succès
▼ ▼
┌────────────────────┐ ┌────────────────────────┐
│ Test Internet Conn │ │ Extract Pipeline Info │
└────────┬───────────┘ └───────────┬────────────┘
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────────────┐
│ Stop - No Internet │ │ Check Jobs API Error (If) │
└────────────────────┘ └──────────┬───────────┬─────┘
│❌ Erreur │✅ Succès
▼ ▼
┌────────────────────┐ ┌──────────────────────────┐
│ Test Internet Conn │ │ Get Pipeline Jobs │
│ 1️⃣ │ └──────────┬──────────────┘
└──────────┬─────────┘ │
▼ ▼
┌────────────────────┐ ┌────────────────────────────┐
│ Stop - No Internet │ │ Process Jobs & Create CSV │
│ 1️⃣ │ └──────────┬─────────────────┘
└────────────────────┘ │
▼
┌────────────────────────────┐
│ Send Discord Report │
│ (Message Markdown) │
└────────────────────────────┘
🧩 Prérequis — Créer les identifiants (Credentials)
1. GitLab — Authentification HTTP
- Dans n8n, allez dans Credentials → New Credential.
- Choisissez HTTP Bearer Auth.
- Collez votre GitLab Token dans le champ Token.
- Donnez-lui un nom (par ex.
GitLab Token). - Enregistrez.
2. Discord — Authentification du bot
- Créez un nouveau Discord Bot API credential sur DISCORD API.
- Se créer une Application et lui affilier un bot Discord

- Accéder à votre espace

- Collez le token du bot Discord.
- Vérifiez que le bot est bien ajouté à votre serveur Discord et qu’il a la permission Send Messages.
Étape 1 : Créer un trigger planifié
- Ajouter le node Schedule Trigger.
- Configurer l’intervalle (ex. toutes les minutes).
Explication technique : Le node Schedule Trigger utilise
cronpour lancer le workflow automatiquement à l’intervalle choisi. Il n’exécute aucune logique métier.
{
"type": "n8n-nodes-base.scheduleTrigger"
}


Étape 2 : Requête HTTP vers GitLab
- Ajouter un node HTTP Request.
- URL :
https://gitlab.com/api/v4/projects/<PROJECT_ID>/pipelines/latest
- Authentification : HTTP Bearer Token.
- Activer
retryOnFailpour gérer les échecs temporaires.
Explication technique : Cette requête utilise le token pour accéder à l’API GitLab et récupérer le dernier pipeline. Le
retryOnFailpermet de relancer automatiquement en cas d’erreur réseau.

Étape 3 : Gestion des erreurs HTTP
- Ajouter un node If après le HTTP Request.
- Condition : vérifier si
$json.error && $json.error.messagen’est pas vide.
- Si oui, stopper ou rediriger le workflow.
- Si non, continuer.
Explication technique : On utilise ici un contrôle conditionnel pour intercepter les erreurs API et éviter que le workflow continue avec des données invalides.

Étape 4 : Vérification de la connexion Internet
- Ajouter un node Execute Command.
- Commande :
LOGFILE="internet_check.log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Test de la connexion Internet..." >> "$LOGFILE"
if ping -c 1 google.fr &> /dev/null
then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Connexion Internet OK" >> "$LOGFILE"
echo "Connexion Internet OK"
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Pas d'accès Internet" >> "$LOGFILE"
echo "HTTP request a échoué : pas d'accès Internet"
fi
- Ajouter un node Stop And Error si la connexion échoue.
Explication technique : La commande shell ping vérifie la connectivité réseau. Les logs sont conservés pour débogage, et le workflow est stoppé pour éviter les erreurs futures.


Étape 5 : Extraction des informations du pipeline
- Ajouter un node Code.
- Extraire les données principales : ID, statut, branche, date de création, URL.
const pipelineData = $input.all()[0].json;
return [{
json: {
pipelineId: pipelineData.id,
pipelineStatus: pipelineData.status,
pipelineRef: pipelineData.ref,
pipelineCreatedAt: pipelineData.created_at,
pipelineWebUrl: pipelineData.web_url
}
}];
Explication technique : Le node Code permet de transformer la réponse brute de l’API en données structurées utilisables par les nodes suivants.

Étape 6 : Récupération des jobs du pipeline
- Ajouter un HTTP Request.
- URL :
https://gitlab.com/api/v4/projects/<PROJECT_ID>/pipelines/{{$json["pipelineId"]}}/jobs
- Authentification Bearer Token.
Explication technique : L’URL utilise la variable
pipelineIdextraite précédemment. Cela permet de récupérer tous les jobs associés au pipeline spécifique.

Étape 7 : Traitement des jobs et génération du rapport
- Ajouter un node Code.
- Exemple de traitement :
- Extraire auteur, commit, statut des jobs.
- Créer un tableau Markdown pour chaque job.
- Générer un message Discord complet.
Explication technique :
map()pour transformer la liste des jobs.filter()pour classer par statut (success, failed, manual).- Utilisation de Markdown pour l’affichage clair sur Discord.

Étape 8 : Envoi du rapport sur Discord
- Ajouter un node Discord.
- Configurer
guildIdetchannelId. - Contenu :
content: "={{ $json['content'] }}"
Explication technique : Le bot Discord utilise l’API pour poster le message. Le contenu est généré dynamiquement par le node précédent.

Étape 9 : Gestion des erreurs API pour les jobs
- Ajouter un node If.
- Vérifier si
$json.error && $json.error.messagen’est pas vide. - En cas d’erreur, relancer le test Internet ou stopper le workflow.
Explication technique : Ceci évite de traiter des données invalides provenant de l’API des jobs.
Étape 10 : Test et validation
- Activer le workflow.
- Vérifier la réception des messages sur Discord.
- Vérifier les logs de connexion Internet.
- Vérifier la gestion des erreurs API et des jobs manuels/échoués.
Bonus techniques
-
Export CSV des jobs pour suivi externe.
-
Notification Slack ou Email en complément.
-
Filtrage des jobs échoués pour alertes rapides.
-
Voici le workflows utilise pour ce tutoriel 😊
{
"name": "My workflow 2",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-2080,
660
],
"id": "442e69e8-32fa-407c-81db-d323534314b8",
"name": "Schedule Trigger"
},
{
"parameters": {
"url": "https://gitlab.com/api/v4/projects/votreprojetid/pipelines/latest",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-1740,
740
],
"id": "43f094d6-6730-4dc4-b84a-0721a956432d",
"name": "HTTP Request",
"retryOnFail": true,
"credentials": {
"httpBearerAuth": {
"id": "",
"name": "Bearer Auth account"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const pipelineData = $input.all()[0].json;\nconst pipelineId = pipelineData.id;\nconst pipelineStatus = pipelineData.status;\nconst pipelineRef = pipelineData.ref;\nconst pipelineCreatedAt = pipelineData.created_at;\nconst pipelineWebUrl = pipelineData.web_url;\n\nreturn [{\n json: {\n pipelineId: pipelineId,\n pipelineStatus: pipelineStatus,\n pipelineRef: pipelineRef,\n pipelineCreatedAt: pipelineCreatedAt,\n pipelineWebUrl: pipelineWebUrl\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-580,
960
],
"id": "4e44f947-b512-4e96-b873-294a6a898791",
"name": "Extract Pipeline Info"
},
{
"parameters": {
"url": "=https://gitlab.com/api/v4/projects/votreprojetid/pipelines/{{$json[\"pipelineId\"]}}/jobs",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-200,
960
],
"id": "f0e7c7e1-57e5-4c85-b4bd-4b893cf35592",
"name": "Get Pipeline Jobs",
"credentials": {
"httpBearerAuth": {
"id": "BLtQcirJO7ieAx3W",
"name": "Bearer Auth account"
}
}
},
{
"parameters": {
"jsCode": "const jobs = items.map(item => item.json);\nconst refJob = jobs[0];\n\n// Infos pipeline\nconst pipelineId = refJob.pipeline?.id || 'Inconnu';\nconst pipelineStatus = refJob.pipeline?.status || 'unknown';\nconst pipelineUrl = refJob.pipeline?.web_url || '#';\nconst pipelineCreated = new Date(refJob.pipeline?.created_at || refJob.created_at).toLocaleString('fr-FR');\n\n// Infos auteur & commit\nconst authorName = refJob.user?.name || 'Auteur inconnu';\nconst authorEmail = refJob.user?.public_email || refJob.commit?.author_email || 'Non fourni';\nconst commitSha = refJob.commit?.short_id || '???';\nconst commitTitle = refJob.commit?.title || 'Aucun titre';\nconst commitUrl = refJob.commit?.web_url || '#';\n\n// Analyse des jobs\nconst manualJobs = jobs.filter(j => j.status === 'manual');\nconst successJobs = jobs.filter(j => j.status === 'success');\nconst failedJobs = jobs.filter(j => j.status === 'failed');\nconst skippedJobs = jobs.filter(j => j.status === 'skipped');\nconst pendingJobs = jobs.filter(j => j.status === 'pending' || j.status === 'created');\nconst runningJobs = jobs.filter(j => j.status === 'running');\n\nconst totalJobs = jobs.length;\n\n// Explication dynamique du statut\nlet statusExplanation = '';\nswitch (pipelineStatus) {\n case 'skipped':\n statusExplanation = `\n• Aucun job ne correspond aux règles \\`only/except\\` ou \\`rules\\` \n• Tous les jobs sont \\`manual\\` \n• Aucun job n’est exécutable dans le contexte actuel.`;\n break;\n case 'failed':\n statusExplanation = `❌ Le pipeline a échoué car un ou plusieurs jobs ont rencontré des erreurs.`;\n break;\n case 'success':\n statusExplanation = `✅ Tous les jobs se sont terminés avec succès.`;\n break;\n case 'manual':\n statusExplanation = `⚠️ Le pipeline contient uniquement des jobs manuels en attente de déclenchement.`;\n break;\n default:\n statusExplanation = `ℹ️ Statut actuel : \\`${pipelineStatus}\\``;\n}\n\n// Construire la table Markdown des jobs\nconst jobsTable = jobs.map(job => {\n const name = job.name || 'N/A';\n const stage = job.stage || 'N/A';\n const status = job.status || 'unknown';\n const emoji = {\n success: '✅',\n failed: '❌',\n skipped: '⏭️',\n manual: '🛠️',\n pending: '⏳',\n running: '🏃♂️'\n }[status] || '🔘';\n return `| \\`${name}\\` | \\`${stage}\\` | ${emoji} ${status.toUpperCase()} |`;\n}).join('\\n');\n\n// Message Discord final\nconst message =\n`🚀 **PIPELINE #${pipelineId} – ${pipelineStatus.toUpperCase()}**\n🔗 ${pipelineUrl}\n\n${statusExplanation}\n\n📋 **Jobs du pipeline** (${totalJobs} au total) :\n\n>>> \n| Nom du Job | Étape | Statut |\n|----------------------|--------------|---------------------------|\n${jobsTable}\n\n🕐 Créé le : ${pipelineCreated}\n\n🧑💻 **Auteur :** ${authorName} \n✉️ **Email :** ${authorEmail} \n🔗 **Commit :** \\`${commitSha}\\` – \"${commitTitle}\" \n${commitUrl}`;\n\nreturn [{\n json: {\n content: message\n }\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-20,
960
],
"id": "9e28a6b7-689e-4a85-b263-c6a892473854",
"name": "Process Jobs & Create CSV"
},
{
"parameters": {
"resource": "message",
"guildId": {
"__rl": true,
"value": "1389683846043402330",
"mode": "list",
"cachedResultName": "Serveur de ᴵᵐᵘ 様 ˑ",
"cachedResultUrl": "https://discord.com/channels/"
},
"channelId": {
"__rl": true,
"value": "1389683846559039755",
"mode": "list",
"cachedResultName": "général",
"cachedResultUrl": "https://discord.com/channels"
},
"content": "={{ $json[\"content\"] }}",
"options": {}
},
"type": "n8n-nodes-base.discord",
"typeVersion": 2,
"position": [
180,
960
],
"id": "cf40705d-dce2-4763-9546-4498fc2bae98",
"name": "Send Discord Report",
"webhookId": "98dcb99d-9cdd-422e-b218-6ac35fcf2d58",
"credentials": {
"discordBotApi": {
"id": "",
"name": "Discord Bot account"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "c4aea13e-8d5f-47d7-83f3-83581060cf22",
"leftValue": "={{ $json.error && $json.error.message ? $json.error.message : '' }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-1520,
740
],
"id": "3c01a389-a4c7-421e-b59e-387f903bccd9",
"name": "Check HTTP Error"
},
{
"parameters": {
"command": "LOGFILE=\"internet_check.log\"\n\necho \"[$(date '+%Y-%m-%d %H:%M:%S')] Test de la connexion Internet...\" >> \"$LOGFILE\"\n\nif ping -c 1 google.fr &> /dev/null\nthen\n echo \"[$(date '+%Y-%m-%d %H:%M:%S')] Connexion Internet OK\" >> \"$LOGFILE\"\n echo \"Connexion Internet OK\"\nelse\n echo \"[$(date '+%Y-%m-%d %H:%M:%S')] HTTP request a échoué : pas d'accès Internet\" >> \"$LOGFILE\"\n echo \"HTTP request a échoué : pas d'accès Internet\"\nfi"
},
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1320,
600
],
"id": "732fdb95-243a-4425-9036-e66bd749b2d4",
"name": "Test Internet Connection",
"executeOnce": true
},
{
"parameters": {
"errorMessage": "❌ ERREUR: Problème de connexion Internet détecté"
},
"type": "n8n-nodes-base.stopAndError",
"typeVersion": 1,
"position": [
-980,
600
],
"id": "b9ddba52-992a-42b2-8664-a0563bd8f143",
"name": "Stop - No Internet"
},
{
"parameters": {
"command": "LOGFILE=\"internet_check.log\"\n\necho \"[$(date '+%Y-%m-%d %H:%M:%S')] Test de la connexion Internet...\" >> \"$LOGFILE\"\n\nif ping -c 1 google.fr &> /dev/null\nthen\n echo \"[$(date '+%Y-%m-%d %H:%M:%S')] Connexion Internet OK\" >> \"$LOGFILE\"\n echo \"Connexion Internet OK\"\nelse\n echo \"[$(date '+%Y-%m-%d %H:%M:%S')] HTTP request a échoué : pas d'accès Internet\" >> \"$LOGFILE\"\n echo \"HTTP request a échoué : pas d'accès Internet\"\nfi"
},
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-160,
660
],
"id": "0b5cc204-ef64-4f3a-b21a-32ee2b020220",
"name": "Test Internet Connection 1"
},
{
"parameters": {
"errorMessage": "❌ ERREUR: Problème de connexion Internet détecté"
},
"type": "n8n-nodes-base.stopAndError",
"typeVersion": 1,
"position": [
80,
660
],
"id": "51cdbf7e-d08c-4043-bcb5-ba7465a4e62a",
"name": "Stop - No Internet 1"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "c4aea13e-8d5f-47d7-83f3-83581060cf22",
"leftValue": "={{ $json.error && $json.error.message ? $json.error.message : '' }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-380,
960
],
"id": "87d17a3b-7479-4ca8-851f-c4a172448f56",
"name": "Check Jobs API Error"
}
],
"pinData": {},
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "Check HTTP Error",
"type": "main",
"index": 0
}
]
]
},
"Extract Pipeline Info": {
"main": [
[
{
"node": "Check Jobs API Error",
"type": "main",
"index": 0
}
]
]
},
"Get Pipeline Jobs": {
"main": [
[
{
"node": "Process Jobs & Create CSV",
"type": "main",
"index": 0
}
]
]
},
"Process Jobs & Create CSV": {
"main": [
[
{
"node": "Send Discord Report",
"type": "main",
"index": 0
}
]
]
},
"Check HTTP Error": {
"main": [
[
{
"node": "Test Internet Connection",
"type": "main",
"index": 0
}
],
[
{
"node": "Extract Pipeline Info",
"type": "main",
"index": 0
}
]
]
},
"Test Internet Connection": {
"main": [
[
{
"node": "Stop - No Internet",
"type": "main",
"index": 0
}
]
]
},
"Test Internet Connection 1": {
"main": [
[
{
"node": "Stop - No Internet 1",
"type": "main",
"index": 0
}
]
]
},
"Check Jobs API Error": {
"main": [
[
{
"node": "Test Internet Connection 1",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Pipeline Jobs",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "4d40dcaf-532e-407e-8efd-183b883cb600",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "62398ea95236fbe75bc3f4d2d6b45822b43cae23beaf2e6690b2ce3b03a1bd54"
},
"id": "UT1n8jK9WblXIkXu",
"tags": []
}
```bash