Ich kenne was, was Du nicht kennst: minimongo

Tales from the Web side  –  0 Kommentare

MongoDB ist eine beliebte und verbreitete NoSQL-Datenbank. Wer ihre API nutzen, aber lieber lokal mit IndexedDB oder LocalStorage arbeiten möchte, kann auf das Node.js-Modul minimongo zurückgreifen, das einen (kleinen) Teil der MongoDB-API implementiert, im Hintergrund aber unter anderem besagte Speichermöglichkeiten nutzen kann.

"Ich kenne was, was Du nicht kennst"

... ist eine gemeinsame Serie von Golo Roden und Philip Ackermann, in der die beiden regelmäßig Module für JavaScript und Node.js vorstellen.

Das Modul ist ursprünglich ein Fork des gleichnamigen Meteor-Moduls minimongo, erweitert dieses aber in verschiedenen Belangen und ermöglicht somit beispielsweise die Verwendung unter Node.js über dessen Package Manager NPM oder die Verwendung im Browser über Browserify.

Verschiedene Persistenzmöglichkeiten

Installiert werden kann das Modul über NPM mit dem Befehl npm install minimongo, eingebunden wie gewohnt über require('minimongo'). Die verschiedenen Persistenzmöglichkeiten, die minimongo unterstützt und durch die MongoDB API abstrahiert sind:

  1. IndexedDB,
  2. LocalStorage,
  3. WebSQL,
  4. lokal im Speicher,
  5. entfernte (echte) MongoDB-Installationen sowie
  6. einen hybriden Ansatz, bei dem ein Teil der Daten lokal, ein anderer remote gespeichert wird.

Analog dazu stellt minimongo verschiedene JavaScript-Objekte bereit (IndexedDb, LocalStorageDb, WebSQLDb, MemoryDb, RemoteDb und HybridDb), von denen man sich je nach Bedarf Objektinstanzen erzeugen kann:

'use strict';
let minimongo = require('minimongo');
let MemoryDb = minimongo.MemoryDb;
let db = new MemoryDb();

Die API ist unabhängig von verwendetem Datenbanktyp natürlich immer gleich und bietet Methoden für das Erstellen und Löschen von Dokumentensammlungen (Collections) und zum Durchführen der üblichen CRUD-Operationen für darin enthaltene Dokumente.

Ein Beispiel

Ein einfaches Beispiel ist in folgendem Listing zu sehen: Hier wird zunächst eine neue Collection ("users") erzeugt und in der entsprechenden Callback-Funktion über die Methode upsert() die zwei Objekte max und moritz hinzugefügt (der Methode upsert() kann dabei statt ein Array auch ein einzelner Wert übergeben werden, wenn man nur ein Objekt hinzufügen möchte). Anschließend wird (wieder innerhalb entsprechender Callback-Funktion) über findOne() eine Anfrage an die Datenbank formuliert, die das erste Objekt zurückgibt, dessen lastName-Eigenschaft den Wert "Mustermann" enthält. Man sieht dabei auch: im Hintergrund wird dem Objekt automatisch eine generierte ID zugewiesen.

let max = {
firstName: 'Max',
lastName: 'Mustermann',
age: 44
};
let moritz = {
firstName: 'Moritz',
lastName: 'Mustermann',
age: 45
};

db.addCollection('users', () => {
db.users.upsert([max, moritz], () =>
{
// Erster Nutzer mit Nachname "Mustermann"
db.users
.findOne({lastName: 'Mustermann'}, {},
(result) => {
console.log(result.firstName); // Max
console.log(result.lastName); // Mustermann
console.log(result._id); // z.B.
// c3f60b7207134c94b5cffeea7ba27db5
},
(error) => {
console.error(error);
}
);
});
});

Möchte man dagegen alle Nutzer ausgeben, bedient man sich der Methoden find() und fetch() wie in folgendem Listing:

// Alle Nutzer mit Nachnamen "Mustermann"
db.users
.find({ lastName:'Mustermann' })
.fetch(
(result) => {
console.log(result);
},
(error) => {
console.error(error);
}
);

Datenbankanfragen konfigurieren

Optional lässt sich über ein Konfigurationsobjekt angeben, nach welcher Eigenschaft die zurückgegebenen Objekte sortiert werden sollen:

// Alle Nutzer mit Nachnamen "Mustermann" sortiert nach Alter
db.users
.find({ lastName:'Mustermann' }, {sort: [['age', 'asc']]}) // 'asc', 'desc'
.fetch(
(result) => {
console.log(result);
console.log(result.length); // 2
},
(error) => {
console.error(error);
}
);

Auch die Anzahl der zurückgegebenen Objekte lässt sich über ein definiertes Limit begrenzen:

// Alle Nutzer mit Nachnamen "Mustermann" mit Limit und sortiert nach Alter
db.users
.find({ lastName:'Mustermann' }, {
sort: [['age', 'asc']],
limit: 1
})
.fetch(
(result) => {
console.log(result);
console.log(result.length); // 1
},
(error) => {
console.error(error);
}
);

Um Objekte aus einer Collection zu löschen, verwendet man die Methode remove(). Diese benötigt allerdings die ID des Objekts, die wie folgt (und eben bereits zu sehen) ermitteln lässt:

db.users
.findOne({firstName: 'Max'}, {},
(result) => {
db.users.remove(
result._id,
(result) => {
console.log('Removed entry');
db.users
.find({ lastName:'Mustermann' })
.fetch(
(result) => {
console.log(result.length); // 1
},
(error) => {
console.error(error);
}
);
},
(error) => {
console.error(error);
}
);
},
(error) => {
console.error(error);
}
);

Fazit

Das Modul minimongo stellt ein einheitliches MongoDB-API für verschiedene, hauptsächlich lokale Datenbanken beziehungsweise Persistenzmöglichkeiten zur Verfügung. Allerdings ist wirklich nur ein Teil der API implementiert. Somit ist es beispielsweise nicht möglich, bestimmte Eigenschaften aus dem Ergebnis auszuschließen, wie es bei MongoDB über Projektionen möglich ist. Auch das Konzept von Cursorn ist (noch) nicht implementiert. Von einer vollwertigen, clientseitigen Alternative zu MongoDB zu sprechen, wäre daher etwas hoch gegriffen. Andere JavaScript-Datenbankbibliotheken, wie PouchDB, dürften da eventuell interessanter sein.

Auch stellt sich die Frage, inwieweit die Änderungen, die an der Originalbibliothek seit dem Zeitpunkt des Forks (Januar 2014) vorgenommen wurden, auch in das Modul übernommen werden.

Trotz alledem stellt das Projekt insgesamt einen interessanten Ansatz dar, gerade, weil verschiedene lokale Datenbanken unterstützt werden.