Beispiele
Drei vollständige, einsatzbereite Beispiele, die die häufigsten FoxServer-Muster demonstrieren: ein minimaler Endpunkt, vollständiges CRUD und eine Master-Detail-Beziehung.
Verwenden Sie diese Beispiele als Ausgangspunkt. Passen Sie Tabellennamen, Felder und Routen an Ihr Projekt an.
Beispiel 1: Hello World
Ein minimaler Controller mit zwei Endpunkten: einer ohne Parameter und einer mit URL-Parameter.
HelloController: ein Gruß mit und ohne Parameter
DEFINE CLASS HelloController AS ApiController OLEPUBLIC
* GET /hello (oeffentlich, kein JWT)
PROCEDURE GetHello(req, res) HELP "GET: hello public"
LOCAL loRes
loRes = THIS.newObject("status,data,message")
loRes.status = "ok"
loRes.data = "Hallo, Welt!"
loRes.message = ""
res.Status(200).Json(THIS.ToJson(loRes))
ENDPROC
* GET /hello/{name} (oeffentlich, mit Parameter)
PROCEDURE GetHelloName(req, res) HELP "GET: hello/{name} public"
LOCAL loRes, lcName
lcName = req.GetParam("name", "Gast")
loRes = THIS.newObject("status,data,message")
loRes.status = "ok"
loRes.data = "Hallo, " + lcName + "!"
loRes.message = ""
res.Status(200).Json(THIS.ToJson(loRes))
ENDPROC
ENDDEFINETest
Mit einem beliebigen HTTP-Client oder im Browser:
curl http://localhost:8099/hello
curl http://localhost:8099/hello/IrwinBeispiel 2: Einfaches CRUD
Erstellen, Lesen, Aktualisieren und Löschen einer Produkttabelle. Zeigt die Verwendung von BeforeEndpoint / AfterEndpoint zum Öffnen und Schließen der Datenbank.
ProductosController: CRUD auf der Tabelle productos
DEFINE CLASS ProductosController AS ApiController OLEPUBLIC
FUNCTION BeforeEndpoint(req, res) AS BOOLEAN
SET DELETED ON
SET EXCLUSIVE OFF
SET SAFETY OFF
SET CENTURY ON
SELECT 0
USE (THIS.cPath + "data\productos") SHARED
RETURN .T.
ENDFUNC
PROCEDURE AfterEndpoint(req, res)
CLOSE DATABASES ALL
ENDPROC
PROCEDURE GetProductos(req, res) HELP "GET: productos public as productos.list"
* Ein ganzer Cursor als JSON, in einer Zeile: TableToJson
* serialisiert ihn, und OkRaw gibt ihn unveraendert zurueck,
* ohne ihn erneut zu parsen. Bei null Zeilen kommt ein leeres
* Array heraus, und das ist richtig so.
SELECT * FROM productos INTO CURSOR tmpProductos NOFILTER
RETURN THIS.OkRaw(THIS.TableToJson("tmpProductos"))
ENDPROC
PROCEDURE GetProducto(req, res) HELP "GET: productos/{id} public"
LOCAL loRes, lcId
lcId = req.params.id
SELECT * FROM productos WHERE id = lcId INTO CURSOR tmpProducto
IF _TALLY = 0
loRes = THIS.newObject("status,data,message")
loRes.status = "error"
loRes.data = NULL
loRes.message = "Produkt nicht gefunden"
res.Status(404).Json(THIS.ToJson(loRes))
ELSE
SCATTER MEMO NAME loRow
loRes = THIS.newObject("status,data,message")
loRes.status = "ok"
loRes.data = loRow
loRes.message = ""
res.Status(200).Json(THIS.ToJson(loRes))
ENDIF
ENDPROC
PROCEDURE CreateProducto(req, res) HELP "POST: productos public"
LOCAL loRes
IF ISNULL(req.json)
loRes = THIS.newObject("status,message")
loRes.status = "error"
loRes.message = "JSON-Body erwartet"
res.Status(400).Json(THIS.ToJson(loRes))
RETURN
ENDIF
LOCAL lcGuid
lcGuid = THIS.NewGuid()
SELECT productos
APPEND BLANK
REPLACE id WITH lcGuid, nombre WITH req.json.nombre, precio WITH req.json.precio, stock WITH req.json.stock
loRes = THIS.newObject("status,message,id")
loRes.status = "ok"
loRes.message = "Produkt erstellt"
loRes.id = lcGuid
res.Status(201).Json(THIS.ToJson(loRes))
ENDPROC
PROCEDURE UpdateProducto(req, res) HELP "PUT: productos/{id} public"
LOCAL loRes, lcId
lcId = req.params.id
IF ISNULL(req.json)
loRes = THIS.newObject("status,message")
loRes.status = "error"
loRes.message = "JSON-Body erwartet"
res.Status(400).Json(THIS.ToJson(loRes))
RETURN
ENDIF
SELECT productos
LOCATE FOR id = lcId
IF !FOUND()
loRes = THIS.newObject("status,message")
loRes.status = "error"
loRes.message = "Produkt nicht gefunden"
res.Status(404).Json(THIS.ToJson(loRes))
RETURN
ENDIF
IF TYPE("req.json.nombre") = "C"
REPLACE nombre WITH req.json.nombre
ENDIF
IF TYPE("req.json.precio") = "N"
REPLACE precio WITH req.json.precio
ENDIF
IF TYPE("req.json.stock") = "N"
REPLACE stock WITH req.json.stock
ENDIF
loRes = THIS.newObject("status,message")
loRes.status = "ok"
loRes.message = "Produkt aktualisiert"
res.Status(200).Json(THIS.ToJson(loRes))
ENDPROC
PROCEDURE DeleteProducto(req, res) HELP "DELETE: productos/{id} public"
LOCAL loRes, lcId
lcId = req.params.id
SELECT productos
LOCATE FOR id = lcId
IF !FOUND()
loRes = THIS.newObject("status,message")
loRes.status = "error"
loRes.message = "Produkt nicht gefunden"
res.Status(404).Json(THIS.ToJson(loRes))
RETURN
ENDIF
DELETE
loRes = THIS.newObject("status,message")
loRes.status = "ok"
loRes.message = "Produkt gelöscht"
res.Status(200).Json(THIS.ToJson(loRes))
ENDPROC
ENDDEFINEJSON-Body für POST/PUT
{
"nombre": "Mechanische Tastatur",
"precio": 89.99,
"stock": 50
} Tabellenstruktur für productos: Dieses Beispiel setzt eine productos.dbf mit Feldern voraus: id (C,36), nombre (C,100), precio (N,10,2), stock (N,6). Passen Sie Typen und Langen an Ihr Schema an.
Beispiel 3: Master-Detail
Eine Bestellung zusammen mit ihren Positionen in einer einzigen hierarchischen JSON-Antwort abrufen.
PedidosController: Bestellungen mit ihren Positionen
DEFINE CLASS PedidosController AS ApiController OLEPUBLIC
FUNCTION BeforeEndpoint(req, res) AS BOOLEAN
SET DELETED ON
SET EXCLUSIVE OFF
SET SAFETY OFF
SET CENTURY ON
SELECT 0
USE (THIS.cPath + "data\pedidos") SHARED
SELECT 0
USE (THIS.cPath + "data\pedido_detalle") SHARED
RETURN .T.
ENDFUNC
PROCEDURE AfterEndpoint(req, res)
CLOSE DATABASES ALL
ENDPROC
PROCEDURE GetPedido(req, res) HELP "GET: pedidos/{id} public"
LOCAL loRes, lcId
lcId = req.params.id
SELECT * FROM pedidos WHERE id = lcId INTO CURSOR tmpPedido
IF _TALLY = 0
loRes = THIS.newObject("status,data,message")
loRes.status = "error"
loRes.data = NULL
loRes.message = "Bestellung nicht gefunden"
res.Status(404).Json(THIS.ToJson(loRes))
RETURN
ENDIF
SELECT * FROM pedido_detalle WHERE pedido_id = lcId INTO CURSOR tmpDetalles
SELECT tmpPedido
SCATTER MEMO NAME loPedido
LOCAL lnDets, i
lnDets = RECCOUNT("tmpDetalles")
loRes = THIS.newObject("status,pedido,message")
loRes.status = "ok"
loRes.pedido = loPedido
loRes.message = ""
i = 0
DIMENSION laDetalles[MAX(lnDets,1)]
SELECT tmpDetalles
SCAN
i = i + 1
SCATTER MEMO NAME loDetalle
laDetalles[i] = loDetalle
ENDSCAN
ADDPROPERTY(loRes, "detalles", @laDetalles)
res.Status(200).Json(THIS.ToJson(loRes))
ENDPROC
* POST /pedidos -- legt eine Bestellung mit ihren Zeilen an
PROCEDURE CreatePedido(req, res) HELP "POST: pedidos public"
LOCAL loRes
* Eine Eigenschaft zu lesen, die nicht kam, wirft Fehler 1734;
* deshalb wird nicht mit ISNULL geprueft, sondern der Zugriff
* eingepackt.
LOCAL lcCliente
lcCliente = ""
TRY
lcCliente = NVL(req.json.cliente, "")
CATCH
ENDTRY
IF !req.HasJsonBody() OR EMPTY(lcCliente)
RETURN THIS.BadRequest("cliente und items sind erforderlich", "bad_request")
ENDIF
LOCAL lcPedidoId, lnTotal, i
lcPedidoId = THIS.NewGuid()
lnTotal = 0
SELECT pedidos
APPEND BLANK
REPLACE id WITH lcPedidoId, fecha WITH DATE(), cliente WITH lcCliente, total WITH 0
* Arrays aus dem Rumpf kommen als Collection an, NICHT als
* VFP-Array: ALEN() scheitert. Durchlaufen wird mit .Count und
* .Item(n).
FOR i = 1 TO req.json.items.Count
LOCAL loItem
loItem = req.json.items.Item(i)
SELECT pedido_detalle
APPEND BLANK
REPLACE pedido_id WITH lcPedidoId, linea WITH i, ;
producto WITH loItem.producto, ;
cantidad WITH loItem.cantidad, ;
precio WITH loItem.precio, ;
importe WITH loItem.cantidad * loItem.precio
lnTotal = lnTotal + (loItem.cantidad * loItem.precio)
ENDFOR
SELECT pedidos
LOCATE FOR id = lcPedidoId
REPLACE total WITH lnTotal
loRes = THIS.newObject("id,total")
loRes.id = lcPedidoId
loRes.total = lnTotal
RETURN THIS.Created(loRes, "/pedidos/" + lcPedidoId)
ENDPROC
ENDDEFINEJSON-Body zum Erstellen einer Bestellung
{
"cliente": "C001",
"items": [
{ "producto": "P001", "cantidad": 2, "precio": 19.99 },
{ "producto": "P002", "cantidad": 1, "precio": 29.99 }
]
} Benotige Tabellen: pedidos (id C36, fecha D, cliente C50, total N10.2) und pedido_detalle (pedido_id C36, linea N4, producto C50, cantidad N8, precio N10.2, importe N12.2).
Best Practices
- Verwenden Sie immer BeforeEndpoint und AfterEndpoint für das Öffnen und Schließen von Tabellen.
- Geben Sie passende HTTP-Codes zurück: 200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Server Error.
- Validieren Sie Eingabedaten vor der Verarbeitung. Vertrauen Sie dem Body niemals ohne Validierung.
- Verwenden Sie THIS.NewGuid() für PKs. Verwenden Sie niemals Auto-Inkrement in gleichzeitigen Umgebungen.
- Innerhalb von TRY/CATCH verwenden Sie EXIT anstelle von RETURN, um den Block zu verlassen.
Weiter: API-Referenz →