häufige Strukturierung mit "Layers" in Web-Anwendungen:
Controller: Empfängt einen HTTP-Request, ruft eine Service-Funktion für die Verarbeitung auf, sendet die Response
Service: Wird durch einen Controller aufgerufen; kann auf andere Services und Models zugreifen; Kann Daten an den Controller zurückliefern; beinhaltet die Haupt-Logik (Geschäftslogik)
Model: Stellt für Services Datenbankzugriffe zur Verfügung
oft entspricht eine Route (HTTP-Anfrage) direkt einem Modell (Datenbank-Tabelle / -Abfrage)
z.B. in einer Shopping-Anwendung:
/products?category=phones&max_price=500
entspricht:
SELECT * FROM products WHERE category = 'phones' AND price <= 500;
manchmal kann einiges im Service Layer geschehen:
Beispiel: Senden einer neuen Nachricht via POST
-Request an /messages
messageService könnte folgendes verwenden:
häufige Struktur in Express-Anwendungen:
kann für einfachere Projekte vereinfacht werden, z.B.:
Beispiel für eine typische Source Code Struktur:
Wir können unsere Routendefinitionen mittels Router
-Objekten strukturieren
alles an einem Ort:
app.get('/accounts', () => {
// ...
});
app.get('/accounts/:id', () => {
// ...
});
app.post('/accounts', () => {
// ...
});
app.get('/transactions', () => {
// ...
});
// ...
saubere Struktur:
app.use('/accounts', accountsRouter);
app.use('/transactions', transactionsRouter);
Definition von accountsRouter
in separater Datei (z.B. routes/accounts.route.js)
import { Router } from 'express';
export const accountsRouter = Router();
accountsRouter.get('/', () => {
// ...
});
accountsRouter.get('/:id', () => {
// ...
});
Themen:
Beispiele für Rollen und Berechtigungen auf einer Nachrichtenseite:
Typischer Auth-Ablauf mit Tokens:
Session-Tokens werden vom Client zum Server in HTTP-Headern gesendet:
Beispiel am Client: Einloggen via Benutzername und Passwort, Erhalten des Tokens:
const loginRes = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'foo',
password: 'bar',
}),
});
const loginData = await loginRes.json();
const token = loginData.token;
Beispiel am Client: mit Hilfe des Tokens auf bestimmte Daten zugreifen:
const notesRes = await fetch('/api/notes', {
headers: { Authorization: `Bearer ${token}` },
});
const notes = await notesRes.json();
ursprüngliche Daten:
name | password
--------+--------------------
alice | 123456
bob | 123456
charlie | abc123
Daten mit Passwort-Hashes:
name | password hash
--------+---------------------------------
Alice | e10adc3949ba59abbe56e057f20f883e
Bob | e10adc3949ba59abbe56e057f20f883e
Charlie | e99a18c428cb38d5f260853678922e03
Hashing-Algorithmen - sortiert von am sichersten zu nicht sicher:
app.post('/api/login', async (req, res) => {
const success = await authService.validateLogin(
req.body.username,
req.body.password
);
if (success) {
const token = authService.createRandomToken();
await authService.saveSession(req.body.username, token);
res.json({ token: token });
} else {
res.status(401).send();
}
});
app.get('/api/notes/:id', async (req, res) => {
const authHeader = req.header('Authorization');
const token = authHeader.split(' ')[1];
const session = await authService.getSession(token);
if (!session) {
// not authenticated
res.status(401).send();
return;
}
const note = await notesService.getNote(req.params.id);
const role = await authService.getRole(session);
if (role === 'user' && session.userId === note.userId) {
res.json(note);
} else if (role === 'admin') {
res.json(note);
} else {
// not authorized
res.status(403).send();
}
});
Zugangsdaten (z.B. für Datenbanken) und Konfiguration werden üblicherweise via Umgebungsvariablen bereitgestellt
Zugangsdaten sollten nicht unter Versionskontrolle stehen
verbreitete Möglichkeit, um Zugangsdaten bereit zu stellen: speichern in einer Datei namens .env, laden als Umgebungsvariablen mittels dotenv
Stelle sicher, dass .env nicht unter Versionskontrolle steht (füge es zur Datei .gitignore hinzu)
Beispiel für .env-Datei:
PORT=3000
NODE_ENV=production
DB_URL=mongodb+srv://...
AUTH0_DOMAIN=xxx.eu.auth0.com
laden in JavaScript:
import dotenv from 'dotenv';
dotenv.config();
const PORT = process.env.PORT;
const NODE_ENV = process.env.NODE_ENV;
Umgebungsvariable NODE_ENV
: spielt z.B. bei express eine wichtige Rolle
in Produktivumgebungen sollte immer NODE_ENV=production
gesetzt sein - ansonsten werden z.B. dem Endnutzer JavaScript-Fehlermeldungen im Detail angezeigt (mit Stack Traces)
Hosting Provider mit kostenlosen Optionen: