Summary for the webdev backend course at HdM Stuttgart
Felicitas Pojtinger
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:
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
/module
Export-Varianten:
Wenn Sie jedoch module.exports direkt zuweisen, werden alle vorherigen Exporte überschrieben.
Import-Varianten:
const { readFile } = require("fs");
readFile();
const { readFile, ...fs } = require("fs");
readFile();
fs.writeFile();
Export:
Default-Export:
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: 3
Import mit destrukturierenden Zuweisung:
import { add as addition, subtract } from "./math.js";
console.log(addition(5, 2)); // Ausgabe: 7
console.log(subtract(5, 2)); // Ausgabe: 3
Callbacks:
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-name
npm i -g paket-name
npm i -D paket-name
package-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.json
npm 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):
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:
In dem Formular muss dann der URL-Parameter
_method=patch
hinzugefügt werden:
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-parser
cookie-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 Lebensdauer
So 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-session
Mit 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.dropCollection
fü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:
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.map
auf
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:
Form:
{name: 'cuisine_1'}
Anlegen eines kombinierten Index:
Form:
{name: 'cuisine_1_address.zipcode_-1'}
Abfragen aller Indizes:
Löschen eines Index:
Löschen aller Indizes:
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 } });