# Extensions (API-Erweiterungen)
Mit dem Extension-System kannst du die SeminarPlan API um eigene Routen, Cronjobs und Logik erweitern, ohne den Core-Code zu verändern.
# Übersicht
Das Extension-System erlaubt es, die API modular zu erweitern. Extensions können:
- Neue Routen registrieren (V1 und V2)
- Bestehende Routen überschreiben (Route Overrides)
- Cronjobs registrieren
- Öffentliche Routen definieren (ohne Authentifizierung)
- Eigene Logik beim Startup ausführen
Extensions werden als Verzeichnisse mit einer plugin.js-Datei angelegt.
# Verzeichnisstruktur
extensions/ # Standard-Verzeichnis (konfigurierbar)
├── meine-erweiterung/
│ ├── plugin.js # Extension-Definition (Pflicht)
│ ├── routes/ # Automatisch geladene Routen (optional)
│ │ ├── contacts.js
│ │ └── webhooks.js
│ └── services/ # Eigene Services (optional)
│ └── MyService.js
└── andere-erweiterung/
└── plugin.js
Das Extensions-Verzeichnis kann per Umgebungsvariable konfiguriert werden:
EXTENSIONS_DIR=/pfad/zu/extensions
# plugin.js -- Extension-Definition
Jede Extension muss eine plugin.js-Datei im Wurzelverzeichnis haben:
module.exports = {
// Metadaten
meta: {
name: 'meine-erweiterung', // Eindeutiger Name (Pflicht)
enabled: true, // Extension aktiv? (Standard: true)
priority: 100, // Ladereihenfolge (niedriger = früher)
routesPrefix: 'v1/', // Prefix für automatisch geladene Routen
},
// Öffentliche Routen (kein Token nötig)
auth: {
publicRoutes: ['/v1/webhooks/stripe'],
publicPrefixes: ['/v1/public/custom/'],
},
// Bestehende Routen überschreiben
overrides: [
{ method: 'GET', url: '/api/v2/contacts' },
],
// Cronjobs registrieren
cron: [
{
name: 'nightly-sync',
cronTime: '0 2 * * *', // Jeden Tag um 02:00
onTick: async () => {
// Synchronisations-Logik
},
},
],
// Registrierungs-Funktion (optional)
register: async (fastify, { app, extension, manager, meta, paths }) => {
// Eigene Logik, Decorators, Hooks etc.
fastify.decorate('myService', new MyService());
},
};
# meta-Felder
| Feld | Typ | Standard | Beschreibung |
|---|---|---|---|
name | string | Verzeichnisname | Eindeutiger Name der Extension |
enabled | boolean | true | Extension aktiv/inaktiv |
priority | number | 100 | Ladereihenfolge (niedrigere Zahl = früher) |
routesPrefix | string | 'v1/' | Prefix für Auto-Loaded Routes |
# Routen hinzufügen
# Automatisch (routes-Verzeichnis)
Dateien im routes/-Verzeichnis der Extension werden automatisch mit @fastify/autoload geladen. Das Format ist identisch zu normalen Fastify-Routen:
// extensions/meine-erweiterung/routes/webhooks.js
module.exports = async function (fastify, opts) {
fastify.post('/webhooks/stripe', {
schema: {
body: { type: 'object' },
},
}, async (request, reply) => {
// Webhook-Logik
return { ok: true };
});
};
# Manuell (in register)
Du kannst Routen auch direkt in der register-Funktion registrieren:
register: async (fastify, { app }) => {
fastify.get('/api/v2/custom/stats', async (request) => {
// Eigene Statistik-Logik
return { items: [], meta: { total: 0 } };
});
},
# Route Overrides
Du kannst bestehende Core-Routen ersetzen. Das ist nützlich, wenn du das Verhalten eines Standard-Endpunkts ändern möchtest:
module.exports = {
meta: { name: 'custom-contacts' },
// Diese Core-Route wird unterdrückt
overrides: [
{ method: 'GET', url: '/api/v2/contacts' },
],
register: async (fastify) => {
// Deine eigene Implementierung
fastify.get('/api/v2/contacts', async (request) => {
// Custom-Logik
return { items: [], meta: { total: 0, limit: 20, offset: 0, hasMore: false } };
});
},
};
Vorsicht bei Route Overrides
- Zwei Extensions können nicht dieselbe Route überschreiben (Fehlermeldung bei Konflikten)
- Du musst das gleiche Response-Format einhalten, sonst bricht das Frontend
- Teste Override-Routen gründlich, da sie Core-Funktionalität ersetzen
# Cronjobs
Extensions können zeitgesteuerte Aufgaben registrieren:
cron: [
// Einfacher Cronjob
{
name: 'cleanup-old-data',
cronTime: '0 3 * * 0', // Sonntags um 03:00
onTick: async () => {
console.log('Bereinigung läuft...');
},
},
// Factory-Funktion (Zugriff auf Fastify-Instanz)
{
factory: async ({ app, fastify }) => ({
name: 'sync-external',
cronTime: '*/30 * * * *', // Alle 30 Minuten
onTick: async () => {
const db = app.mongo.db;
// Synchronisations-Logik mit DB-Zugriff
},
}),
},
],
# Öffentliche Routen
Standardmäßig benötigen alle Routen ein gültiges Token. Extensions können Routen als öffentlich markieren:
auth: {
// Exakte Routen ohne Auth
publicRoutes: ['/v1/webhooks/stripe', '/v1/webhooks/paypal'],
// Alle Routen unter diesem Prefix ohne Auth
publicPrefixes: ['/v1/public/integration/'],
},
# Register-Funktion
Die register-Funktion erhält folgende Parameter:
| Parameter | Beschreibung |
|---|---|
fastify | Gekapselte Fastify-Instanz (für Routen, Decorators, Hooks) |
app | Root Fastify-Instanz (für globale Services) |
extension | Das vollständige Extension-Objekt |
manager | Der ExtensionManager (für fortgeschrittene Fälle) |
meta | Die Metadaten der Extension |
paths.dir | Verzeichnis der Extension |
paths.file | Pfad zur plugin.js |
paths.routesDir | Pfad zum routes/-Verzeichnis (oder null) |
# Lebenszyklus
- Discover -- ExtensionManager durchsucht das Extensions-Verzeichnis nach
plugin.js-Dateien - Load -- Jede Extension wird geladen und validiert
- Sort -- Extensions werden nach
prioritysortiert (niedrig zuerst) - Collect -- Öffentliche Routen und Overrides werden gesammelt
- Guard -- Core-Route-Guards werden installiert (überschriebene Routen werden übersprungen)
- Register -- Extensions werden der Reihe nach registriert (register-Funktion + Auto-Load Routes)
- Cron -- Cronjobs werden materialisiert und gestartet
# Best Practices
Empfehlungen
- Gib deiner Extension einen eindeutigen, beschreibenden Namen
- Verwende
prioritynur wenn die Ladereihenfolge wichtig ist - Halte Route Overrides minimal -- erweitere lieber, als zu ersetzen
- Teste Extensions isoliert, bevor du sie in die Produktion bringst
- Nutze die
register-Funktion für komplexe Initialisierung - Dokumentiere öffentliche Routen sorgfältig (Sicherheit!)
# Siehe auch
- API-Dokumentation -- REST-Endpunkte und Formate
- Entwickler-Übersicht -- Technischer Hintergrund