Eigene REST-Schnittstellen¶
Bisher haben die Skripte andere Systeme aufgerufen. Mit einem REST-Service ist es umgekehrt: Andere Systeme — ein Portal, eine Fachanwendung, ein Automatisierungs-Werkzeug — rufen KCForms auf, etwa um Daten abzufragen oder einen Antrag anzulegen.
Ein REST-Service ist ein kleines Skript-Modul. Angelegt wird es unter System-Administration → Schnittstellen & Verteilung → REST-Services; nach dem Speichern ist es sofort erreichbar. Ausführliches dazu: REST-Services.
So liest du die Beispiele
Jedes Beispiel ist ein vollständiger Service zum Einfügen in den
Editor. Darunter steht, wie man ihn aufruft — zum Beispiel mit curl
oder direkt in der eingebauten Swagger-UI (/api/rest/docs).
<server> steht für die Adresse deiner KCForms-Installation.
Die Adresse eines Aufrufs setzt sich zusammen aus
/api/rest/service + path des Service + path der Operation.
Nur für Administratoren
Ein REST-Service darf alles, was ein Skript darf — Datenbank, Dateien, Mails. Den Aufruf sicherst du je Service ab (Beispiele 15 und 16).
Einsteiger¶
1. Hallo Welt¶
module.exports = {
path: "/beispiel/hallo",
operations: [
{
id: "hallo",
method: "GET",
handler: function () {
return { nachricht: "Hallo Welt" };
}
}
]
};
Aufruf: curl http://<server>/api/rest/service/beispiel/hallo →
{"nachricht":"Hallo Welt"}
Ein zurückgegebenes Objekt wird automatisch zu JSON.
2. Einen Parameter mitgeben¶
Angaben hinter dem ? der Adresse stehen im Objekt query.
module.exports = {
path: "/beispiel/gruss",
operations: [
{
id: "gruss",
method: "GET",
handler: function () {
return { text: "Hallo, " + (query.name || "Unbekannt") + "!" };
}
}
]
};
Aufruf: …/beispiel/gruss?name=Anna
3. Ein Teil der Adresse als Wert¶
{nr} im Pfad ist ein Platzhalter — sein Wert steht in pathParameters.
module.exports = {
path: "/beispiel/artikel",
operations: [
{
id: "artikel",
path: "/{nr}",
method: "GET",
handler: function () {
return { nummer: pathParameters.nr, bezeichnung: "Artikel " + pathParameters.nr };
}
}
]
};
Aufruf: …/beispiel/artikel/4711
4. Einfacher Text statt JSON¶
Gibt der Handler einen Text zurück, kommt genau dieser Text an.
module.exports = {
path: "/beispiel/ping",
operations: [
{ id: "ping", method: "GET", handler: function () { return "pong"; } }
]
};
5. Die Uhrzeit des Servers¶
module.exports = {
path: "/beispiel/zeit",
operations: [
{
id: "zeit",
method: "GET",
handler: function () {
var jetzt = new Date();
return { datum: jetzt.format("dd.MM.yyyy"), uhrzeit: jetzt.format("HH:mm:ss") };
}
}
]
};
6. Mehrere Operationen in einem Service¶
Ein Service kann viele Adressen bedienen — hier eine Liste und einen einzelnen Eintrag.
var farben = ["rot", "gruen", "blau"];
module.exports = {
path: "/beispiel/farben",
operations: [
{ id: "alle", method: "GET", handler: function () { return farben; } },
{ id: "eine", method: "GET", path: "/{nr}",
handler: function () { return { farbe: farben[Number(pathParameters.nr)] }; } }
]
};
Hilfsvariablen wie farben stehen einfach oberhalb von module.exports.
Fortgeschritten¶
7. Daten empfangen (POST)¶
Was der Aufrufer mitschickt, steht in body. body.asString() liefert den
Text, JSON.parse macht daraus ein Objekt.
module.exports = {
path: "/beispiel/rechner",
operations: [
{
id: "addieren",
method: "POST",
handler: function () {
var daten = JSON.parse(body.asString());
return { summe: daten.a + daten.b };
}
}
]
};
Aufruf:
curl -X POST -H "Content-Type: application/json" -d '{"a":12,"b":30}' http://<server>/api/rest/service/beispiel/rechner
8. Fehlende Angaben zurückweisen¶
Response.badRequest(…) antwortet mit Status 400 („fehlerhafte
Anfrage"). Mit throw bricht der Handler dabei sofort ab.
module.exports = {
path: "/beispiel/anmeldung",
operations: [
{
id: "anmelden",
method: "POST",
handler: function () {
var daten = body.isEmpty() ? {} : JSON.parse(body.asString());
if (!daten.name) {
throw Response.badRequest({ fehler: "Name fehlt" });
}
return { ok: true, willkommen: daten.name };
}
}
]
};
9. „Nicht gefunden" melden¶
Für Unbekanntes gibt es Status 404.
var laender = { DE: "Deutschland", AT: "Österreich", SE: "Schweden" };
module.exports = {
path: "/beispiel/land",
operations: [
{
id: "land",
path: "/{code}",
method: "GET",
handler: function () {
var name = laender[pathParameters.code];
if (!name) throw Response.notFound({ fehler: "Unbekannter Ländercode" });
return { code: pathParameters.code, name: name };
}
}
]
};
10. Aus der Datenbank lesen¶
Alle Werkzeuge der anderen Kapitel stehen auch hier bereit.
module.exports = {
path: "/beispiel/statistik",
operations: [
{
id: "statistik",
method: "GET",
handler: function () {
return {
benutzer: kc.num(db.scalar("SELECT COUNT(*) FROM USERS")),
stand: new Date().format("yyyy-MM-dd HH:mm")
};
}
}
]
};
11. Suchen mit Parameter¶
Werte aus der Anfrage gehören nie direkt in den SQL-Text — das ? ist ein
sicherer Platzhalter.
module.exports = {
path: "/beispiel/benutzer",
operations: [
{
id: "suche",
method: "GET",
handler: function () {
var begriff = "%" + (query.suche || "") + "%";
var zeilen = db.query(
"SELECT LOGIN_NAME FROM USERS WHERE LOGIN_NAME LIKE ? ORDER BY LOGIN_NAME", begriff);
return zeilen.slice(0, 20).map(function (z) { return z.login_name; });
}
}
]
};
12. Eine CSV-Datei liefern¶
Mit Response.ok(…).type(…) bestimmst du den Dateityp selbst.
module.exports = {
path: "/beispiel/export",
operations: [
{
id: "export",
method: "GET",
handler: function () {
var text = "Artikel;Menge\nStift;10\nBlock;5\n";
return Response.ok(text)
.type("text/csv; charset=UTF-8")
.header("Content-Disposition", "attachment; filename=\"export.csv\"");
}
}
]
};
13. Kopfzeilen des Aufrufers lesen¶
HTTP-Kopfzeilen („Header") stehen in headers — Namen immer klein
geschrieben.
module.exports = {
path: "/beispiel/sprache",
operations: [
{
id: "gruss",
method: "GET",
handler: function () {
var sprache = String(headers["accept-language"] || "de").substring(0, 2);
var gruesse = { de: "Hallo", en: "Hello", sv: "Hej" };
return { gruss: gruesse[sprache] || gruesse.de };
}
}
]
};
14. „Angelegt" mit Status 201¶
Wird etwas neu angelegt, ist 201 Created die passende Antwort.
module.exports = {
path: "/beispiel/ticket",
operations: [
{
id: "anlegen",
method: "POST",
handler: function () {
var daten = JSON.parse(body.asString());
var ticket = { nummer: "T-" + Date.now(), titel: daten.titel, status: "offen" };
return Response.created(ticket);
}
}
]
};
15. Mit Token absichern¶
Stelle im Editor unter Authentifizierung „Bearer-Token" ein und trage einen Token ein. Ohne diesen Token antwortet der Service mit 401.
module.exports = {
path: "/beispiel/geheim",
operations: [
{
id: "geheim",
method: "GET",
handler: function () {
return { daten: "nur mit Token", aufrufer: request.user };
}
}
]
};
Aufruf: curl -H "Authorization: Bearer <token>" http://<server>/api/rest/service/beispiel/geheim
16. Mit Benutzer und Passwort absichern¶
„Basic" arbeitet mit Benutzername und Passwort — sie gehören zum Service,
es sind keine KCForms-Konten. request.user enthält den Namen.
module.exports = {
path: "/beispiel/intern",
operations: [
{
id: "intern",
method: "GET",
handler: function () {
return { angemeldet_als: request.user };
}
}
]
};
Aufruf: curl -u portal:<passwort> http://<server>/api/rest/service/beispiel/intern
Profi¶
Das Übungsformular
Die folgenden Beispiele speichern echte Datensätze im Formular
uebung_bestellung mit den Feldern kunde (Text, Pflicht), betrag
(Zahl), status (Auswahl neu/freigegeben/erledigt) und datum
(Datum) — siehe Datensätze.
Jeder Aufruf beginnt von vorn
Variablen im Modul werden bei jedem Aufruf neu angelegt. Was bleiben
soll, speicherst du mit forms oder db.
17. Einen Datensatz anlegen¶
Ein Portal übergibt eine Bestellung; KCForms legt den Datensatz an und meldet die neue Nummer zurück.
module.exports = {
path: "/beispiel/bestellung",
operations: [
{
id: "anlegen",
method: "POST",
handler: function () {
var daten = JSON.parse(body.asString());
var id = forms.create("uebung_bestellung", {
kunde: daten.kunde,
betrag: daten.betrag,
status: "neu",
datum: new Date().format("yyyy-MM-dd")
});
return Response.created({ id: id });
}
}
]
};
18. Ungültige Daten abweisen¶
forms.validate prüft gegen das Formular. Status 422 heißt: „Ich habe
dich verstanden, aber die Daten passen nicht."
module.exports = {
path: "/beispiel/bestellung",
operations: [
{
id: "anlegen",
method: "POST",
handler: function () {
var daten = body.isEmpty() ? {} : JSON.parse(body.asString());
var werte = { kunde: daten.kunde, betrag: daten.betrag, status: "neu" };
var fehler = forms.validate("uebung_bestellung", werte);
if (fehler.length > 0) {
throw Response.status(422, { fehler: fehler });
}
return Response.created({ id: forms.create("uebung_bestellung", werte) });
}
}
]
};
19. Lesen, Ändern, Löschen¶
Ein vollständiger Service für Bestellungen: Liste, einzelner Datensatz, Status ändern, löschen — je nach HTTP-Methode.
var FORM = "uebung_bestellung";
function holeOder404(id) {
var satz = forms.get(FORM, Number(id));
if (!satz) throw Response.notFound({ fehler: "Bestellung " + id + " gibt es nicht" });
return satz;
}
module.exports = {
path: "/beispiel/bestellungen",
operations: [
{ id: "liste", method: "GET",
handler: function () {
return forms.list(FORM).map(function (s) {
return { id: s.id, kunde: s.kunde, betrag: s.betrag, status: s.status };
});
} },
{ id: "eine", method: "GET", path: "/{id}",
handler: function () { return holeOder404(pathParameters.id); } },
{ id: "status", method: "PATCH", path: "/{id}",
handler: function () {
holeOder404(pathParameters.id);
var neu = JSON.parse(body.asString()).status;
forms.update(FORM, Number(pathParameters.id), { status: neu }, { validate: true });
return holeOder404(pathParameters.id);
} },
{ id: "loeschen", method: "DELETE", path: "/{id}",
handler: function () {
holeOder404(pathParameters.id);
forms.delete(FORM, Number(pathParameters.id));
return Response.noContent();
} }
]
};
| Aufruf | Wirkung |
|---|---|
GET …/beispiel/bestellungen |
alle Bestellungen |
GET …/beispiel/bestellungen/7 |
Bestellung 7 |
PATCH …/beispiel/bestellungen/7 mit {"status":"erledigt"} |
Status ändern |
DELETE …/beispiel/bestellungen/7 |
löschen (Antwort 204, ohne Inhalt) |
20. Konflikte melden¶
Status 409 („Konflikt") passt, wenn etwas schon existiert.
module.exports = {
path: "/beispiel/kunde",
operations: [
{
id: "anlegen",
method: "POST",
handler: function () {
var name = JSON.parse(body.asString()).kunde;
var vorhanden = forms.list("uebung_bestellung").some(function (s) { return s.kunde === name; });
if (vorhanden) {
throw Response.status(409, { fehler: "Kunde " + name + " ist schon vorhanden" });
}
return Response.created({ id: forms.create("uebung_bestellung", { kunde: name, status: "neu" }) });
}
}
]
};
21. Fehler mit eigenem Fehlercode¶
Für aufrufende Programme ist ein fester Fehlercode leichter auszuwerten als ein Text. Zusätzliche Felder am Fehler werden mitgeschickt.
module.exports = {
path: "/beispiel/limit",
operations: [
{
id: "pruefen",
method: "GET",
handler: function () {
var betrag = Number(query.betrag || 0);
if (betrag > 1000) {
var e = new Error("Betrag über dem Limit von 1.000 €");
e.code = "LIMIT_UEBERSCHRITTEN";
e.limit = 1000;
throw e;
}
return { ok: true, betrag: betrag };
}
}
]
};
Ein throw new Error(…) führt zu Status 500. Soll es ein anderer Status
sein, nimm Response.status(…) wie in Beispiel 20.
22. Die Schnittstelle beschreiben¶
summary, description und die Parameter-Angaben erscheinen in der
Swagger-UI unter /api/rest/docs — so können andere die Schnittstelle
ohne Rückfrage nutzen.
module.exports = {
path: "/beispiel/umrechnen",
documentation: {
info: { title: "Währungsrechner", version: "1.0", description: "Rechnet Euro in Kronen um." }
},
operations: [
{
id: "umrechnen",
method: "GET",
summary: "Euro in Kronen umrechnen",
description: "Multipliziert den Euro-Betrag mit dem angegebenen Kurs.",
tags: ["rechner"],
queryParameters: [
{ name: "euro", schema: { type: "number" }, description: "Betrag in Euro" },
{ name: "kurs", schema: { type: "number" }, description: "Kronen je Euro" }
],
handler: function () {
var euro = Number(query.euro), kurs = Number(query.kurs);
if (isNaN(euro) || isNaN(kurs)) throw Response.badRequest({ fehler: "euro und kurs müssen Zahlen sein" });
return { euro: euro, kurs: kurs, kronen: Math.round(euro * kurs * 100) / 100 };
}
}
]
};
23. Ein Lebenszeichen für die Überwachung¶
Überwachungs-Werkzeuge fragen regelmäßig ab, ob ein Dienst läuft. Status 503 signalisiert „gerade nicht verfügbar".
module.exports = {
path: "/beispiel/status",
operations: [
{
id: "status",
method: "GET",
handler: function () {
var dbOk;
try {
db.scalar("SELECT 1");
dbOk = true;
} catch (e) {
dbOk = false;
}
var antwort = { dienst: "KCForms", datenbank: dbOk ? "OK" : "GESTÖRT", zeit: new Date().toISOString() };
return dbOk ? antwort : Response.status(503, antwort);
}
}
]
};