Introduction
Une CMDB (Configuration Management Database) est le cœur de la gestion ITIL : elle stocke les Configuration Items (CI) comme serveurs, applications ou réseaux, et leurs relations (dépendances, hébergements). Sans elle, tracer les impacts d'un incident devient chaotique, comme naviguer sans carte dans un data center.
Ce tutoriel beginner vous montre comment créer une CMDB fonctionnelle en Node.js avec Express (API REST) et Prisma (ORM sur SQLite). Pourquoi cette stack ? SQLite pour zéro config DB (idéal dev local), Prisma pour schémas type-sûrs, Express pour routes rapides. À la fin, vous avez une API CRUD complète : listez, créez, liez des CI. Exemple concret : modélisez un serveur web hébergeant une app, avec relations automatisées.
Important pour pros : cette base scale vers PostgreSQL. Temps estimé : 30 min. Valeur : bookmarquez pour vos audits ITIL.
Prérequis
- Node.js 20+ installé (téléchargez ici)
- Connaissances basiques en JavaScript (variables, fonctions async)
- Éditeur de code (VS Code recommandé avec extension Prisma)
- Terminal (PowerShell ou bash)
- 5 min pour setup
Initialiser le projet et installer les dépendances
mkdir cmdb-simple
cd cmdb-simple
npm init -y
npm install express @prisma/client prisma typescript ts-node @types/express @types/node
npm install -D @types/node
npx prisma init --datasource-provider sqliteCette commande crée le dossier projet, initialise package.json, installe Express pour l'API, Prisma pour l'ORM et TypeScript pour le typage. L'init Prisma génère prisma/schema.prisma et .env avec DATABASE_URL pour SQLite. Exécutez-la d'un coup : votre setup est prêt en 1 min, sans pièges de versions.
Comprendre le schéma CMDB
Avant le code, conceptualisons : une CMDB a CI (ex: 'Serveur-Web-01', type 'Serveur') et Relations (ex: 'héberge' vers 'App-Ecommerce'). Analogie : CI sont des nœuds, relations des arêtes dans un graphe. On utilise deux modèles Prisma liés par foreign keys pour éviter les doublons et assurer l'intégrité.
Définir les modèles Prisma pour CI et Relations
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model CI {
id String @id @default(cuid())
name String @unique
type String // ex: Serveur, Application, Reseau
description String?
relationsAsFrom Relation[] @relation("FromTo")
relationsAsTo Relation[] @relation("ToFrom")
}
model Relation {
id String @id @default(cuid())
type String // ex: heberge, depend, connecte
ciFromId String
ciToId String
ciFrom CI @relation("FromTo", fields: [ciFromId], references: [id], onDelete: Cascade)
ciTo CI @relation("ToFrom", fields: [ciToId], references: [id], onDelete: Cascade)
@@unique([ciFromId, ciToId, type])
}Ce schéma définit CI avec nom unique et type, plus Relations bidirectionnelles pour graphe. @@unique évite doublons relations. Cascade supprime auto les orphelins. Piège : sans onDelete, suppressions plantent ; testez avec npx prisma db push après édition.
Appliquer la migration et générer le client Prisma
npx prisma db push
npx prisma generatedb push crée la DB SQLite sans migration formelle (idéal proto). generate compile le client TS. Résultat : ./prisma/dev.db existe avec tables. Vérifiez avec sqlite3 prisma/dev.db ".schema" – zéro erreur si OK.
Créer le serveur Express avec typage TS
Maintenant, l'API : endpoints REST pour CI (list, create) et relations. On utilise async/await pour clarté, Prisma pour queries type-sûres. Port 3001 pour éviter conflits.
Implémenter le serveur principal et routes CI
import express from 'express';
import { PrismaClient } from '@prisma/client';
const app = express();
const prisma = new PrismaClient();
app.use(express.json());
const PORT = 3001;
// GET /cis - Liste tous les CI avec relations
app.get('/cis', async (req, res) => {
try {
const cis = await prisma.cI.findMany({
include: {
relationsAsFrom: true,
relationsAsTo: true,
},
});
res.json(cis);
} catch (error) {
res.status(500).json({ error: 'Erreur serveur' });
}
});
// POST /ci - Créer un CI
app.post('/ci', async (req, res) => {
try {
const { name, type, description } = req.body;
const ci = await prisma.cI.create({
data: { name, type, description },
});
res.json(ci);
} catch (error) {
res.status(400).json({ error: 'CI existant ou données invalides' });
}
});
app.listen(PORT, () => {
console.log(`Serveur CMDB sur http://localhost:${PORT}`);
});Ce serveur expose GET /cis (avec include pour graphe complet) et POST /ci. Try/catch gère erreurs Prisma (ex: unique violation). Lancez avec npx ts-node server.ts – curl http://localhost:3001/cis vide au début. Piège : oubliez express.json() et POST body parse pas.
Ajouter les routes pour les Relations
// Ajoutez ces routes à server.ts après les routes CI
// POST /relation - Créer une relation
app.post('/relation', async (req, res) => {
try {
const { type, ciFromId, ciToId } = req.body;
// Vérif existence CI
const ciFrom = await prisma.cI.findUnique({ where: { id: ciFromId } });
const ciTo = await prisma.cI.findUnique({ where: { id: ciToId } });
if (!ciFrom || !ciTo) {
return res.status(404).json({ error: 'CI non trouvé' });
}
const relation = await prisma.relation.create({
data: { type, ciFromId, ciToId },
include: { ciFrom: true, ciTo: true },
});
res.json(relation);
} catch (error) {
res.status(400).json({ error: 'Relation dupliquée ou invalide' });
}
});
// DELETE /relation/:id
app.delete('/relation/:id', async (req, res) => {
try {
await prisma.relation.delete({ where: { id: req.params.id } });
res.json({ message: 'Relation supprimée' });
} catch (error) {
res.status(404).json({ error: 'Relation non trouvée' });
}
});Collez ces routes dans server.ts. POST vérifie CI existants avant création (intégrité). Include renvoie CI liés. DELETE par ID. Test : créez 2 CI, puis relation ; GET /cis montre graphe. Piège : sans vérif, foreign key errors crashent.
Script de seeding pour données d'exemple
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// Créer CI exemples
const serveur = await prisma.cI.create({
data: { name: 'Serveur-Web-01', type: 'Serveur', description: 'Nginx sur Ubuntu' },
});
const app = await prisma.cI.create({
data: { name: 'App-Ecommerce', type: 'Application', description: 'React + Node' },
});
const bd = await prisma.cI.create({
data: { name: 'DB-Prod-01', type: 'Base de Données', description: 'PostgreSQL' },
});
// Créer relations
await prisma.relation.create({
data: { type: 'heberge', ciFromId: serveur.id, ciToId: app.id },
});
await prisma.relation.create({
data: { type: 'depend', ciFromId: app.id, ciToId: bd.id },
});
console.log('Seed OK: 3 CI, 2 relations');
}
main()
.finally(async () => {
await prisma.$disconnect();
});Ce script peuple la DB avec exemples réalistes (serveur → app → DB). Lancez npx ts-node seed.ts avant API. Vérifiez : curl GET /cis montre objets nested. Piège : oubliez $disconnect, connexions leak en dev.
Package.json avec scripts prêts
{
"name": "cmdb-simple",
"version": "1.0.0",
"scripts": {
"dev": "ts-node server.ts",
"seed": "ts-node seed.ts",
"db:push": "prisma db push",
"generate": "prisma generate"
},
"dependencies": {
"express": "^4.19.2",
"@prisma/client": "^5.14.0",
"prisma": "^5.14.0",
"typescript": "^5.5.3",
"ts-node": "^10.9.2",
"@types/express": "^4.17.21",
"@types/node": "^22.0.0"
}
}Copiez ce package.json complet (remplacez l'auto-généré). npm run dev lance serveur, run seed popule. Versions pinned pour reproductibilité 2026. Piège : deps manquantes cassent ts-node.
Tester votre CMDB
- Lancez
npm run seed npm run dev- curl -X POST http://localhost:3001/ci -H "Content-Type: application/json" -d '{"name":"Reseau-01","type":"Reseau"}'
- curl http://localhost:3001/cis (voir graphe)
- curl -X POST http://localhost:3001/relation -H "Content-Type: application/json" -d '{"type":"connecte","ciFromId":"
","ciToId":" "}'
Bonnes pratiques
- Validation inputs : Ajoutez Zod pour schemas (npm i zod) – ex: z.object({name: z.string().min(1)})
- Pagination : Sur GET /cis, ajoutez skip/take pour >100 CI
- Auth : Intégrez JWT (npm i jsonwebtoken) sur routes sensibles
- Logs : Utilisez Winston pour tracer queries Prisma
- Backup : Script cron copiant prisma/dev.db
Erreurs courantes à éviter
- Oubli include : GET /cis sans relations montre CI isolés – toujours nested pour vue graphe
- Migrations manuelles : db push OK dev, mais utilisez migrate deploy en prod
- Connexions leak : Toujours prisma.$disconnect() en scripts
- CORS : Ajoutez app.use(cors()) si frontend distant
- Unique violations : Testez noms CI uniques avant POST
Pour aller plus loin
- Migrez vers PostgreSQL : changez url en DB prod
- Visualisez graphe : Intégrez Cytoscape.js frontend
- ITIL avancé : Ajoutez Status, Owner fields
- Outils pros : ServiceNow API ou iTop open-source