Summary for the webdev backend course at HdM Stuttgart
2023-01-28
These study materials are heavily based on professor Toenniessen’s “Web Development Backend” lecture at HdM Stuttgart and prior work of fellow students.
Found an error or have a suggestion? Please open an issue on GitHub (github.com/pojntfx/uni-webdev-backend-notes):
If you like the study materials, a GitHub star is always appreciated :)
Uni Webdev Backend Notes (c) 2023 Felicitas Pojtinger and contributors
SPDX-License-Identifier: AGPL-3.0
Vorteile:
Sehr einfache APIs, schnell zu lernen
Nachteile:
Node.js hat ein Modul-Konzept, das es ermöglicht, Funktionen und Variablen in eigene Dateien auszulagern und sie in anderen Dateien zu importieren.
Ein Beispiel dafür ist die Funktion add(x,y), die in
eine separate Datei namens 01d_Export.js ausgelagert
wird:
module.exports = function add(x, y) {
return x + y;
};In einer anderen Datei, z.B. 01d_AddiererFctModule.js, wird das Modul importiert und verwendet:
const add = require("./01d_Export");
const a = 5,
b = 7;
const s = add(a, b);
console.log(`${a} + ${b} = ${s}`);require/moduleExport-Varianten:
module.exports = function add(x, y) {
return x + y;
};module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
};exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;Wenn Sie jedoch module.exports direkt zuweisen, werden alle vorherigen Exporte überschrieben.
Import-Varianten:
const fs = require("fs");
fs.readFile();
const readFile = require("fs").readFile;
readFile();const { readFile } = require("fs");
readFile();
const { readFile, ...fs } = require("fs");
readFile();
fs.writeFile();Export:
export function add(x, y) {
return x + y;
}
export function subtract(x, y) {
return x - y;
}Default-Export:
export default (x, y) {
return x - y;
}Import eines gesamten Moduls:
import * as math from "./math.js";
console.log(math.add(5, 2)); // Ausgabe: 7
console.log(math.subtract(5, 2)); // Ausgabe: 3Import mit destrukturierenden Zuweisung:
import { add as addition, subtract } from "./math.js";
console.log(addition(5, 2)); // Ausgabe: 7
console.log(subtract(5, 2)); // Ausgabe: 3Callbacks:
const fs = require("fs");
fs.readFile("file.txt", function (err, data) {
if (err) throw err;
console.log(data);
});Promises:
const fs = require("fs").promises;
fs.readFile("file.txt")
.then((data) => console.log(data))
.catch((err) => console.error(err));async/await:
const fs = require("fs").promises;
async function readFileExample() {
try {
const data = await fs.readFile("file.txt");
console.log(data);
} catch (err) {
console.error(err);
}
}
readFileExample();Mit Support für ein paar wenige MIME-Types:
const http = require("http");
const fs = require("fs");
const { extname } = require("path");
const app = http.createServer((request, response) => {
fs.readFile(__dirname + request.url, (err, data) => {
const status = err ? 400 : 200;
if (extname(request.url) == ".html")
response.writeHead(200, { status, "Content-Type": "text/html" });
if (extname(request.url) == ".js")
response.writeHead(200, { status, "Content-Type": "text/javascript" });
if (extname(request.url) == ".css")
response.writeHead(200, { status, "Content-Type": "text/css" });
response.write(data);
response.end();
});
});
app.listen(3000);
console.log("Listening on :3000");Mit Support für ein alle MIME-Types:
$ npm install node-static
const http = require("http");
const fileserver = new (require("node-static").Server)();
const app = http.createServer((request, response) => {
fileserver.serve(request, response);
});
app.listen(3000);
console.log("Listening on :3000");Mit Support für ein alle MIME-Types & Express:
$ npm install express
const express = require("express");
const app = express();
app.use("/WDBackend", express.static(__dirname + "/public"));
app.listen(3000);
console.log("Listening on :3000");require
importiert werden, ohne dass ein relativer Pfad angegeben
werden muss.npm install paket-name oder
npm i paket-namenpm i -g paket-namenpm i -D paket-namepackage-lock.json enthält die exakten
Versionen aller installierten Abhängigkeiten.node_modules Ordner enthält die Dateien
aller installierten Pakete.node_modules Ordner sollte immer von
Git-Commits ausgeschlossen werdenpackage-lock.json sollte hingegen immer
committed werden.package.jsonnpm init erstellt werdenscripts in der package.json Datei
können Command-Line Befehle gespeichert werden, die später ausgeführt
werden können, indem man sie in der Kommandozeile aufruft, z.B.
npm run start oder npm run test.Best Practice: Pakete sollten immer im Projekt installiert werden, damit dort alle Abhängigkeiten definiert sind. CLI-Tools können auch global, z.B. zur Projektinitialisierung, installiert werden.
Jahr 2000: Überlastung der Web-Backends (Server)
Heute: Zustandslose Web-Backends (Server)
Sichere und idempotente Schnittstellen:
Unsichere und nicht-idempotente Schnittstelle: POST (Create auf eine Ressource). Im Gegensatz zu DELETE können hier nach einem erneuten Anruf ohne Checks weitere Objekte erstellt werden.
Pfad:
http://127.0.0.1:3000/fruits
const DATA = [
{ id: 1, name: "Apfel", color: "gelb,rot" },
{ id: 2, name: "Birne", color: "gelb,grün" },
{ id: 3, name: "Banane", color: "gelb" },
];
app.get("/fruits", (req, res) => {
res.send(DATA);
});URL-Parameter:
http://127.0.0.1:3000/fruits/2
const DATA = [
{ id: 1, name: "Apfel", color: "gelb,rot" },
{ id: 2, name: "Birne", color: "gelb,grün" },
{ id: 3, name: "Banane", color: "gelb" },
];
app.get("/fruits/:id", (req, res) => {
const id = parseInt(req.params.id);
const item = DATA.find((o) => o.id === id);
res.send(item);
});Query-Parameter:
http://127.0.0.1:3000/fruits/2
const DATA = [
{ id: 1, name: "Apfel", color: "gelb,rot" },
{ id: 2, name: "Birne", color: "gelb,grün" },
{ id: 3, name: "Banane", color: "gelb" },
];
app.get("/fruits", (req, res) => {
const id = parseInt(req.query.id);
const item = DATA.find((o) => o.id === id);
res.send(item);
});HTTP-Body (URL-Encoded)
Nutze
express.urlencoded
app.use(express.urlencoded({ extended: true }));
const DATA = [{ id: 1, name: "Apfel", color: "gelb,rot" }];
app.post("/fruits", (req, res) => {
const { name, color } = req.body;
if (DATA.find((o) => o.name === name)) {
res.send("Duplicate name");
} else {
const id = Math.max(...DATA.map((o) => o.id)) + 1;
const fruit = { id, name, color };
DATA.push(fruit);
res.send(fruit);
}
});HTTP-Body (JSON)
Nutze
express.json
app.use(express.json());
const DATA = [{ id: 1, name: "Apfel", color: "gelb,rot" }];
app.post("/fruits", (req, res) => {
const { name, color } = req.body;
if (DATA.find((o) => o.name === name)) {
res.send("Duplicate name");
} else {
const id = Math.max(...DATA.map((o) => o.id)) + 1;
const fruit = { id, name, color };
DATA.push(fruit);
res.send(fruit);
}
});app.get("/ab?cd", function (req, res) {
res.send("ab?cd");
}); // acdabcd
app.get("/ab+cd", function (req, res) {
res.send("ab+cd");
}); // abcdabbbbcd
app.get("/ab.*cd", function (req, res) {
res.send("ab.*cd");
}); // abcdabxcd
app.get("/ab(cd)?e", function (req, res) {
res.send("ab(cd)?e");
}); // /abe und /abcde
app.get(/a/, function (req, res) {
res.send("/a/");
}); // alles mit 'a' drin
app.get(/.*fly$/, function (req, res) {
res.send("/.*fly$/");
});Wildcard-Route:
app.all(/.*/, (req, res, next) => {
console.log(`wildcard-route: ${req.method} ${req.url}`);
next();
});Middleware (empfohlen):
app.use((req, res, next) => {
console.log(`middleware: ${req.method} ${req.url}`);
next();
});Die next()-Methode führt immer den nächsten passenden
Routen-Handler aus.
let cb0 = function (req, res, next) {
console.log("CB0");
next();
};
let cb1 = function (req, res, next) {
console.log("CB1");
next();
};
let cb2 = function (req, res) {
res.send("Hello from CB2!");
};
app.get("/example/c", [cb0, cb1, cb2]);Wichtig: next nicht vergessen!
Mehrere HTTP-Verben für eine Route können mithilfe von Chaining Routes zusammengefasst werden.
app
.route("/books")
.get(function (req, res) {
res.send("Get all books");
})
.post(function (req, res) {
res.send("Add a book");
});
app
.route("/books/:id")
.put(function (req, res) {
res.send("Update the book");
})
.delete(function (req, res) {
res.send("Delete the book");
});Vorteile: weniger fehleranfällig, leichter zu pflegen (da man die Route nur einmal schreibt)
Modularisierung von Routen in Express kann mithilfe von
express.Router erreicht werden.
Erstellung einer Router-Datei birds.js:
const express = require("express");
const router = express.Router();
// Middleware
router.use(function timeLog(req, res, next) {
console.log("Time: ", Date.now());
next();
});
// Routen
router.get("/", function (req, res) {
res.send("Birds home page");
});
router.get("/about", function (req, res) {
res.send("About birds");
});
module.exports = router;Einbindung des Routers in die Anwendung app.js:
const express = require("express");
const app = express();
const birds = require("./birds");
app.use("/birds", birds);
app.listen(3000);
console.log("Listening on :3000");Result:
res.status(code): Setzt den HTTP-Statuscode der Antwort
(z.B. 200 für erfolgreiche Anfrage, 404 für nicht gefunden)res.redirect(url): Leitet den Request an eine andere
URL umres.cookie(key, value, options): Setzt ein Cookie im
Browser des Users, optionale Parameter können angegeben werden wie z.B.
die Dauer des Cookies und ob es sicher übertragen werden sollres.attachment(path_to_file): Sendet eine Datei als
Attachment (z.B. Download)res.download(path_to_file): Sendet eine Datei zum
Download und zeigt eine entsprechende Benachrichtigung im Browser des
UsersRequest:
req.headers(): Gibt ein Objekt mit allen
HTTP-Request-Headern zurückreq.cookies(): Gibt ein Objekt mit allen Cookies
zurück, die im Request enthalten sind (benötigt die Middleware
cookie-parser)404 als JSON zurückgeben:
app.use("/users", require("./routes/users"));
app.use("/products", require("./routes/products"));
// Middleware nach allen Routes
app.use((req, res) => {
res.status(404);
res.json({ message: "Not found" });
});Exceptions:
try {
throw new Error("Something went wrong");
} catch (err) {
res.status(500).json({ message: "InternalServerError" });
}app.get("/", async (req, res, next) => {
try {
throw new Error("Something went wrong");
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
res.status(500);
res.json({ message: "InternalServerError" });
console.error(err);
});const express = require("express");
const methodOverride = require("method-override");
const app = express();
app.use(methodOverride("_method"));Jetzt kann man eine PATCH-Route definieren, die dann auch über ein Formular angesprochen werden kann:
app.patch("/fruits", (req, res) => {
// some code ...
});In dem Formular muss dann der URL-Parameter
_method=patch hinzugefügt werden:
<form action="/fruits?_method=patch" method="post">...</form>Jetzt wird die PATCH-Route aufgerufen, wenn das Formular abgeschickt wird.
Der Code auf dem Server, der die EJS-Template-Engine verwendet, sieht wie folgt aus:
const express = require("express");
const app = express();
app.set("view engine", "ejs");
app.get("/user", (req, res) => {
const user = {
name: "John Doe",
email: "johndoe@example.com",
phone: "555-555-5555",
};
res.render("user-template", { user });
});Das Template template.ejs im
Unterverzeichnis views, welcher ein JavaScript-Objekt
{vorname, adresse, telefon} übergeben wird:
<html>
<body>
<h1>User Information</h1>
<table>
<tr>
<td>Name:</td>
<td><%= user.name %></td>
</tr>
<tr>
<td>Email:</td>
<td><%= user.email %></td>
</tr>
<tr>
<td>Phone:</td>
<td><%= user.phone %></td>
</tr>
</table>
</body>
</html>Server:
const express = require("express");
const app = express();
app.set("view engine", "ejs");
const DATA = [
{ id: 1, name: "Apfel", color: "gelb,rot" },
{ id: 2, name: "Birne", color: "gelb,grün" },
{ id: 3, name: "Banane", color: "gelb" },
];
app.get("/fruits", (req, res) => {
res.render("all", { fruits: DATA }); // all.ejs Template
});
app.get("/fruits/:id", (req, res) => {
const id = parseInt(req.params.id);
const fruit = DATA.find((o) => o.id === id);
res.render("fruit", fruit); // fruit.ejs Template
});
app.listen(3000);
console.log("EJS server running on localhost:3000");Template:
<html>
<body>
<table>
<tr>
<th>Name</th>
<th>Farbe</th>
</tr>
<% fruits.forEach( o => { %>
<tr>
<td><%= o.name %></td>
<td><%= o.color %></td>
</tr>
<% }) %>
</table>
</body>
</html>cookie-parsercookie-parser ermöglicht zustandsbehaftete
ServerSo können Cookies gesetzt werden:
const cookieParser = require("cookie-parser");
app.use(cookieParser());
response.cookie("userID", "xyz12345"); // Einzelner Cookie
response
.cookie("userID", "xyz12345")
.cookie("verein", "VfB Stuttgart", { maxAge: 90000 }); // Mehrere Cookies, der zweite mit 90000 milli secs LebensdauerSo können Cookies ausgelesen werden:
const cookieParser = require("cookie-parser");
app.use(cookieParser());
const cookies = request.cookies;
let userID = cookies.userID;
let verein = cookies.verein;express-sessionMit dem npm-Package express-session kann man
zustandsbehaftete Server bauen:
const express = require("express");
const session = require("express-session");
const app = express();
app.use(
session({
secret: "mykey", // Für Encoding und Decoding des Cookies
resave: false, // Nur speichern nach Änderung
saveUninitialized: true, // Anfangs immer speichern
cookie: { maxAge: 5000 }, // Ablaufzeit in Millisekunden
})
);
app.get("/", function (req, res) {
if (req.session.count) {
// Eine Session kann beliebige Attribute bekommen
req.session.count++;
res.setHeader("Content-Type", "text/html");
res.write("<p>count: " + req.session.count + "</p>");
res.end();
} else {
req.session.count = 1;
res.end("Willkommen zu der Sitzung. Refresh!");
}
});Zuerst mongodb installieren:
npm i -s mongodb
Dann mit DB verbinden:
let db = null;
const url = `mongodb://localhost:27017`;
MongoClient.connect(url, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then((connection) => {
db = connection.db("food");
console.log("connected to database food ...");
});Erstellen einer Collection:
app.post("/example-create-collection-fruits", async (req, res) => {
await db.createCollection("fruits");
res.send("Collection fruits created ...");
});Löschen einer Datenbank:
app.post("/example-drop-db-food", async (req, res) => {
await db.dropDatabase("food");
res.send("Database food dropped!");
});
db.dropCollectionfür das Löschen einer Collection
Importieren von Dokumenten:
$ mongoimport --db tools-test --collection restaurants --file restaurants.json
Exportieren von Dokumenten:
$ mongoexport --db tools-test --collection restaurants --out new-restaurants.json
Einfügen eines Dokument:
app.post("/example-create/fruits", async (req, res) => {
const { name, color } = req.body;
const fruit = { name, color };
await db.collection("fruits").insertOne(fruit);
res.send(`${name} inserted ...`);
});Auslesen eines Dokuments:
app.get("/example-find-one/fruits/:name", async (req, res) => {
const { name } = req.params;
const fruit = await db.collection("fruits").findOne({ name });
if (fruit) {
res.send(fruit);
} else {
res.status(400).send("not found ...");
}
});Einfügen mehrerer Dokumente:
app.post("/example-insert-many/fruits", async (req, res) => {
const fruits = [
{ name: "Apfel", color: "gelb,rot" },
{ name: "Birne", color: "gelb,grün" },
{ name: "Kiwi", color: "grün" },
{ name: "Banane", color: "gelb" },
{ name: "Pfirsich", color: "gelb,rot" },
];
await db.collection("fruits").insertMany(fruits);
res.send("Fruits inserted");
});Auslesen aller Dokumente:
app.get("/example-list/fruits", async (req, res) => {
const fruits = await db.collection("fruits").find().toArray();
res.send(fruits);
});Löschen eines Dokuments:
app.delete("/example-delete/fruits/:name", async (req, res) => {
const { name } = req.params;
const result = await db.collection("fruits").deleteOne({ name });
if (result.deletedCount > 0) {
res.send(`${name} deleted ...`);
} else {
res.status(400).send("fruit not found, nothing to delete ...");
}
});Löschen mehrerer Dokumente:
db.collection("fruits").deleteMany({ name: { $regex: name } });Aktualisierung von Dokumenten:
app.patch("/example-update-cuisine/:name", async (req, res) => {
const { name } = req.params;
const { cuisine } = req.body;
const result = await db.collection("restaurants").updateOne(
{ name },
{
$set: { cuisine },
$currentDate: { lastModified: true }, // Änderungsdatum
}
);
res.send(result);
});Hinzufügen von Arrayelementen in Dokumenten:
app.post("/example-push-grade-score/restaurants/:name", async (req, res) => {
const { name } = req.params;
const { grade, score } = req.body;
const newGrade = { date: new Date(), grade, score };
const result = await db.collection("restaurants").updateOne(
{ name },
(update = {
$push: { grades: newGrade },
$currentDate: { lastModified: true },
})
);
res.send(result);
});Löschen von Arrayelementen in Dokumenten:
app.post("/example-pop-grade-score/restaurants/:name", async (req, res) => {
const { name } = req.params;
const query = { name };
const result = await db.collection("restaurants").updateOne(query, {
$pop: { grades: 1 },
$currentDate: { lastModified: true },
});
res.send(result);
});$pop {<array>: 1} entfernt das letzte
Element,$pop {<array>: -1} entfernt das erste Element des
Arrays.Tiefe Queries:
app.get("/example-zip/restaurants", async (req, res) => {
const { cuisine, zip } = req.query;
const restaurants = await db
.collection("restaurants")
.find({ "address.zipcode": zip, cuisine })
.toArray();
res.send(restaurants.map((o) => ({ name: o.name, zip: o.address.zipcode })));
});BSON für Vergleichsoperatoren:
app.get("/example-zip-range/restaurants", async (req, res) => {
const { zipMin, zipMax, cuisine } = req.query;
const restaurants = await db
.collection("restaurants")
.find({
cuisine,
"address.zipcode": { $gte: zipMin, $lt: zipMax }, // es gibt auch $eq, $in, $neq, $nin ...
})
.toArray();
res.send(restaurants.map((o) => ({ name: o.name, zip: o.address.zipcode })));
});BSON für Oder-Verknüpfung:
app.get("/example-zip-or-cuisine/restaurants", async (req, res) => {
const { zip, cuisine } = req.query;
const restaurants = await db
.collection("restaurants")
.find(
{ $or: [{ "address.zipcode": zip }, { cuisine }] } // es gibt auch noch $and, $not und $nor
)
.toArray();
res.send(
restaurants.map((o) => ({
name: o.name,
cuisine: o.cuisine,
zip: o.address.zipcode,
}))
);
});result.mapauf
JavaScript-ArraysEin/Auschluss von Attributen:
app.get("/example-fields/restaurants", async (req, res) => {
const { borough, cuisine } = req.query;
const restaurants = await db
.collection("restaurants")
.find(
{ borough, cuisine },
{ projection: { name: 1, address: 1, _id: 0 } } // 1 bedeuted Einschluss eines Attributs, 0 den Ausschluss
)
.toArray();
res.send(restaurants);
});Sortieren:
app.get("/example-sort/restaurants", async (req, res) => {
const { borough, cuisine } = req.query;
const restaurants = await db
.collection("restaurants")
.find({ borough, cuisine }, { projection: { name: 1, address: 1, _id: 0 } })
.sort({ name: 1 }) // Mit 1 wird aufsteigend sortiert, mit 0 absteigend.
.toArray();
res.send(restaurants);
});Aggregation:
app.get("/example-avg-score/restaurants", async (req, res) => {
const { borough, cuisine } = req.query;
const restaurants = await db
.collection("restaurants")
.aggregate([
{
// Wie `WHERE` in SQL
$match: { borough, cuisine },
},
{
// Wie `SELECT` in SQL
$project: {
name: "$name", // auch name: 1 möglich
avg_score: { $avg: "$grades.score" },
}, // auch $min, $max, $sum
},
{ $sort: { name: 1 } },
])
.toArray();
res.send(restaurants);
});Gruppierung:
app.get("/example-group/restaurants", async (req, res) => {
const { borough, cuisine } = req.query;
const restaurants = await db
.collection("restaurants")
.aggregate([
{
$match: { borough, cuisine },
},
{
// Wie `GROUP BY` in SQL
$group: {
_id: "$address.zipcode",
count: { $sum: 1 }, // auch $min, $max, $avg
},
},
{ $sort: { _id: 1 } },
])
.toArray();
res.send(restaurants);
});Datenbank-Indizes beschleunigen die Zugriffe für Queries und
Updates, wenn nicht konkret mit der _id gesucht
wird.
Anlegen eines Index:
await db.collection("restaurants").createIndex({ cuisine: 1 }); // 1: Aufsteigend, -1: AbsteigendForm:
{name: 'cuisine_1'}
Anlegen eines kombinierten Index:
await db
.collection("restaurants")
.createIndex({ cuisine: 1, "address.zipcode": -1 });Form:
{name: 'cuisine_1_address.zipcode_-1'}
Abfragen aller Indizes:
const indexes = await db.collection("restaurants").getIndexes();Löschen eines Index:
const result = await db.collection("restaurants").dropIndex("cuisine_1"); // 0: not ok, 1: successLöschen aller Indizes:
const result = await db.collection("restaurants").dropIndexes();Vorteile:
Nachteile:
mongodb in Node.jsIst sehr ähnlich zu MongoDB:
const url = "mongodb://localhost:27017/food_mongoose";
mongoose
.connect(url, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => {
console.log("connected to database food_mongoose ...");
});Definition von Schemata:
const mongoose = require("mongoose");
const fruitSchema = new mongoose.Schema({
name: { type: String, required: true },
color: { type: String, required: true },
img: { data: Buffer, contentType: String },
});
const Fruit = mongoose.model("Fruit", fruitSchema); // `Fruit` maps to a collection `fruits`Schema Types aus ES6:
const schemaExample = new mongoose.Schema({
bool: Boolean,
updated: Date,
age: Number,
array: [],
arrayofString: [String],
arrayofArrays: [[]],
arrayofArrayOfNumbers: [[Number]],
map: Map,
mapOfString: { type: Map, of: String },
});Weitere Schema Types aus MongoDB:
const schemaExample = new mongoose.Schema({
mixed: mongoose.Mixed, // Kann alles sein (`any`)
_someId: mongoose.ObjectId, // Explizite MongoDB-Id
decimal: mongoose.Decimal128, // 128-bit Floating-Point; `mongoose.Types.Decimal128.fromString('3.1415')`
});Indizes in Schemas:
const schemaExample = new mongoose.Schema({
name: {
type: String,
required: true, // Feld zwingend notwendig
index: true, // Index wird automatisch angelegt
},
});Options-Objekt:
const schemaExample = new mongoose.Schema(
{
name: String,
age: Number,
},
{
// Das Options-Objekt
timestamps: true, // Automatisch `createdAt` und `modifiedAt` verwalten
}
);Verschachtelte Schemata mit Kopien:
const ratingSchema = new mongoose.Schema({
grade: {
type: Number,
min: 1,
max: 6,
},
comment: String,
date: Date,
});
const productSchema = new mongoose.Schema({
name: { type: String, required: true },
price: { type: String, required: true },
ratings: [ratingSchema],
});
const Rating = mongoose.model("Rating", ratingSchema);
const Product = mongoose.model("Product", productSchema);Verschachtelte Schemata mit Referenzen:
const productSchema = new mongoose.Schema({
name: String,
category: {
// 1:1-Beziehung
type: mongoose.ObjectId,
ref: "Category",
},
});
const Product = mongoose.model("Product", productSchema);
const categorySchema = new mongoose.Schema({
name: String,
products: [
// 1:n-Beziehung
{
type: mongoose.ObjectId,
ref: "Product",
},
],
});
const Category = mongoose.model("Category", categorySchema);Auslesen von Referenzen mit
populate(wie SQLJOIN):await Product.find().populate("category")
Auslesen aller Dokumente:
Fruit.find().lean() liefert nur einfache JS-Objekte für
bessere Performanceapp.get("/example-list/fruits", async (req, res) => {
const fruits = await Fruit.find();
res.send(fruits);
});Hinzufügen eines Dokuments:
app.post("/example-create/fruits", async (req, res) => {
const { name, color } = req.body;
const doc = await Fruit.findOne({ name });
if (doc) {
res.status(400).send("fruit found, delete first ...");
return;
}
const fruit = new Fruit({ name, color });
const imgPath = path.join(IMAGE_PATH, `${name}.png`);
try {
fruit.img.data = await fs.readFile(imgPath);
fruit.img.contentType = "image/png";
} catch (err) {
console.log(`No image for ${name} found.`);
}
await fruit.save();
res.send(`${name} inserted ...`);
});Umwandlung von MongoDB-Queries in Mongoose-Queries:
app.get("/example-group-by-color/fruits/:color", async (req, res) => {
const { color } = req.params;
const result = await Fruit.aggregate([
{
$match: { color: { $regex: color } },
},
{
$group: {
_id: "$color",
count: { $sum: 1 },
},
},
{ $sort: { _id: 1 } },
]);
res.send(result);
});Ersetze
db.collection("fruits")durchFruit
Eingebaute Validatoren:
const breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, "Too few eggs"],
max: 12,
},
bacon: {
type: Number,
required: [true, "Why no bacon?"],
},
drink: {
type: String,
enum: ["Coffee", "Tea"],
required: function () {
return this.bacon > 3;
},
},
});Eigene Validatoren:
function myValidator(val) {
return val === "something";
}
new mongoose.Schema({ name: { type: String, validate: myValidator } });
// Hinzufügen einer Error-Message, {PATH} ist der fehlerhafte Pfad im Schema:
const customValidator = [
myValidator,
'Ups, {PATH} does not equal "something".',
];
new mongoose.Schema({ name: { type: String, validate: customValidator } });