La bibliothèque Node.js
@ished/client parle à IshedDB depuis vos serveurs Node.js (version 22 ou plus récente). Elle s'écrit comme le pilote MongoDB pour Node.js : le code qui l'utilise change à peine. Elle n'a aucune dépendance. Installation : voir Premiers pas.
Dans les exemples de cette page, client est la connexion de la première section et db une base : const db = client.db('Clinique'). Chaque exemple est exécuté contre un vrai serveur IshedDB par nos tests.
Se connecter
import { connect } from '@ished/client';
const client = connect({
url: process.env.ISHED_URL, // https://… (http seulement vers votre propre machine)
key: process.env.ISHED_KEY, // ished_sk_… : une clé du projet (page Accès)
cluster: 'Cluster0', // facultatif : sans nom, le premier cluster du projet
timeoutMs: 30_000, // facultatif : la durée maximale d'une requête
});
const db = client.db('Clinique'); // ou client.base('Clinique')connect ne fait aucune requête : la première part avec le premier appel. Une clé ne doit jamais aller dans un navigateur ni dans une application mobile : appelez IshedDB depuis vos serveurs.
Les valeurs
Un document est un objet JavaScript. Les dates (Date), les entiers au-delà de 2^53 (BigInt) et les octets (Buffer, Uint8Array) gardent leur type à l'aller comme au retour. L'identifiant _id est un texte : donné, il est gardé ; absent, IshedDB en fabrique un.
const db = client.db('Clinique');
const r = await db.collection('dossiers').insertOne({ _id: 'd-001', ouvert: new Date('2026-11-12T08:00:00Z'), numero: 9007199254740993n, scan: Buffer.from('PDF…') });
const d = await db.collection('dossiers').findOne({ _id: r.insertedId });
console.log(d.ouvert instanceof Date, typeof d.numero, Buffer.isBuffer(d.scan)); // true bigint trueBases et collections
| Appel | Ce qu'il fait |
|---|---|
client.listDatabases() | les bases du cluster : { databases: [{ name, sizeOnDisk, empty }], totalSize } (une clé limitée : les siennes) |
db.listCollections().toArray() | les collections et les vues : name, type (collection ou view), options |
db.collection(nom) | une collection (créée avec son premier document) |
db.createCollection(nom, options) | une collection vide, avec son validator ; ou une vue avec viewOn et pipeline |
db.dropCollection(nom) | supprime la collection et ses documents, ou la vue |
db.command({ collMod, … }) | change une vue ou les règles de validation d'une collection |
Lire
const patients = client.db('Clinique').collection('patients');
await patients.insertMany([
{ nom: 'Awa Diop', ville: 'Dakar', âge: 34, tags: ['cardio'] },
{ nom: 'Ibrahima Sow', ville: 'Dakar', âge: 61, tags: ['diabète', 'cardio'] },
]);
const un = await patients.findOne({ nom: 'Awa Diop' });
const curseur = patients.find({ ville: 'Dakar', âge: { $gte: 30 } }, { projection: { nom: 1, _id: 0 } }).sort({ âge: -1 }).limit(10);
for await (const p of curseur) console.log(p.nom); // lus 1000 par 1000, au fil de la boucle
console.log(await patients.countDocuments({ tags: 'cardio' }), await patients.distinct('ville'));
console.log(un.âge);- Filtres : égalité,
$eq $ne $gt $gte $lt $lte $in $nin $exists $type $all $size $elemMatch $regex $mod $not $and $or $nor $expr, la recherche plein texte$text(avec un index texte) ; une valeur cherchée dans une liste la trouve, comme dans MongoDB. - Projection : les champs gardés (
{ nom: 1 }) ou retirés ({ notes: 0 },{ _id: 0 }), pas les deux. - Curseurs :
.sort(),.limit(),.skip(),.project(),.toArray()(100 000 documents au plus : au-delà, parcourez le curseur),.explain()(le plan suivi : index utilisé ou lecture complète). estimatedDocumentCount(),countDocuments(filtre),distinct(champ, filtre).
Écrire
const patients = client.db('Clinique').collection('patients');
await patients.updateOne({ nom: 'Awa Diop' }, { $set: { 'adresse.ville': 'Dakar' }, $inc: { visites: 1 }, $push: { tags: 'suivi' } });
await patients.updateOne({ nom: 'Khady Ba' }, { $set: { ville: 'Ziguinchor' } }, { upsert: true });
await patients.updateMany({ ville: 'Dakar' }, { $currentDate: { vuLe: true } });
await patients.replaceOne({ nom: 'Ibrahima Sow' }, { nom: 'Ibrahima Sow', ville: 'Thiès', âge: 61 });
await patients.deleteOne({ nom: 'Khady Ba' });
const lot = await patients.bulkWrite([
{ insertOne: { document: { nom: 'Ndeye Diouf', ville: 'Kaolack' } } },
{ updateOne: { filter: { nom: 'Ndeye Diouf' }, update: { $set: { âge: 45 } } } },
]);
console.log(lot.insertedCount, lot.modifiedCount); // 1 1Opérateurs de modification : $set $unset $inc $mul $min $max $rename $currentDate $setOnInsert $push $addToSet $pull $pullAll $pop. insertMany envoie 1000 documents par requête. bulkWrite s'arrête au premier refus, sauf avec { ordered: false } ; un refus lève une erreur dont details.writeErrors dit lesquels.
Index
const patients = client.db('Clinique').collection('patients');
await patients.createIndex({ ville: 1, âge: -1 });
await patients.createIndex({ notes: 'text' }, { default_language: 'french' });
console.log((await patients.indexes()).map((i) => i.name)); // [ '_id_', 'ville_1_âge_-1', 'notes_text' ]
const comptes = client.db('Clinique').collection('comptes');
await comptes.createIndex({ email: 1 }, { unique: true, name: 'email_unique' });Composés, uniques, partiels (partialFilterExpression), texte (15 langues, sans tenir compte des accents ni des majuscules), « wildcard » ({ "$**": 1 }) et d'expiration (une définition avec expireAt). dropIndex(nom), dropIndexes().
Agrégations
const patients = client.db('Clinique').collection('patients');
const parVille = await patients.aggregate([
{ $match: { âge: { $gte: 18 } } },
{ $group: { _id: '$ville', n: { $sum: 1 }, âgeMoyen: { $avg: '$âge' } } },
{ $sort: { n: -1 } },
]).toArray();
console.log(parVille);Les étapes de MongoDB : $match $project $addFields $set $unset $group $sort $limit $skip $count $unwind $lookup $facet $bucket $bucketAuto $sortByCount $replaceRoot $replaceWith $sample, avec leurs expressions. Les résultats se lisent 1000 par 1000.
Vues et validation
const db = client.db('Clinique');
await db.createCollection('patients_dakar', { viewOn: 'patients', pipeline: [{ $match: { ville: 'Dakar' } }] });
console.log(await db.collection('patients_dakar').countDocuments());
await db.createCollection('ordonnances', {
validator: { $jsonSchema: { required: ['patient', 'date'], properties: { patient: { bsonType: 'string' } } } },
validationAction: 'error',
});Une vue se lit comme une collection, sans s'écrire. Un document qui ne suit pas le validateur est refusé (ou écrit avec un avertissement au journal, avec validationAction: 'warn').
Transactions
const db = client.db('Clinique');
const session = client.startSession();
await session.withTransaction(async (s) => {
await db.collection('stocks').updateOne({ _id: 'insuline' }, { $inc: { reste: -1 } }, { session: s, upsert: true });
await db.collection('sorties').insertOne({ produit: 'insuline', quand: new Date() }, { session: s });
});
await session.endSession();Tout ou rien, sur plusieurs collections et plusieurs bases d'un cluster ; withTransaction recommence en cas de conflit. startTransaction, commitTransaction et abortTransaction existent aussi.
Le temps réel
const flux = db.collection('rendezvous').watch([{ $match: { operationType: 'insert' } }]);
for await (const changement of flux) {
console.log(changement.fullDocument);
}Comme les change streams de MongoDB : watch() sur une collection, une base (db.watch()) ou le cluster (client.watch()), avec un pipeline, resumeAfter (le _id d'un changement reçu, pour reprendre après une coupure ; les changements sont gardés 24 heures), fullDocument: 'updateLookup', et les documents d'avant et d'après un changement pour une collection créée avec changeStreamPreAndPostImages. flux.close() l'arrête.
La syntaxe propre à IshedDB
En plus des méthodes de MongoDB, chaque collection offre des requêtes en chaîne, à la manière de Firestore :
import { connect, increment, remove, serverTime } from '@ished/client';
const client = connect({ url: process.env.ISHED_URL, key: process.env.ISHED_KEY });
const rdv = client.db('Clinique').collection('rendezvous');
const ref = await rdv.add({ patient: 'awa', service: 'cardiologie', jour: new Date('2026-12-01') });
await rdv.doc(ref.id).update({ rappels: increment(1), note: remove(), vuLe: serverTime() });
const prochains = await rdv.where('service', '==', 'cardiologie').orderBy('jour').limit(20).get();
console.log(prochains.length, await rdv.where('patient', '==', 'awa').exists());
console.log(await rdv.countBy('service'));where(champ, op, valeur) avec == != < <= > >= in not-in array-contains array-contains-any exists missing matches, orderBy, limit, offset, select, get(), first(), count(), exists(), countBy(champ), pages(), updateAll, updateFirst, deleteFirst, delete() ; doc(id).get(), create, set, update, delete, onSnapshot ; les valeurs spéciales increment, multiply, atLeast, atMost, remove, rename, serverTime, arrayUnion, arrayRemove, arrayAppend, arrayPop. Un document lu ainsi est { id, data, createTime, updateTime }.
Les définitions
Avec une clé d'administration, une collection reçoit sa définition : champs et types, champs obligatoires, valeurs par défaut, champs uniques, index, champs cachés (jamais renvoyés sans être demandés, et chaque révélation va au journal), horodatage automatique, expiration.
await client.db('Clinique').collection('comptes').define({
fields: { email: { type: 'string', required: true, lowercase: true }, motDePasse: { type: 'string', hidden: true } },
timestamps: true,
unique: ['email'],
});Les erreurs
Un refus lève une IshedError : code (invalid-argument, unauthenticated, permission-denied, not-found, already-exists, failed-precondition, resource-exhausted…), message, status (le statut HTTP), requestId (à donner à l'assistance) et parfois details (les champs refusés et pourquoi).
import { connect, IshedError } from '@ished/client';
const client = connect({ url: process.env.ISHED_URL, key: process.env.ISHED_KEY });
const comptes = client.db('Clinique').collection('patients');
try {
await comptes.insertOne({ _id: 'p-1', nom: 'Awa' });
await comptes.insertOne({ _id: 'p-1', nom: 'Awa encore' });
} catch (e) {
if (e instanceof IshedError && e.code === 'already-exists') console.log('déjà là');
else throw e;
}