Beispiele
Drei vollstandige, einsatzbereite Beispiele, die die haufigsten FoxServer-Muster demonstrieren: ein minimaler Endpunkt, vollstandiges 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.
DEFINE CLASS HelloController AS ApiController OLEPUBLIC
* GET /api/hello (offentlich, 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 /api/hello/{name} (offentlich, 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
ENDDEFINE Test
Mit einem beliebigen HTTP-Client oder im Browser:
curl http://localhost:8080/api/hello
curl http://localhost:8080/api/hello/Irwin Beispiel 2: Einfaches CRUD
Erstellen, Lesen, Aktualisieren und Loschen einer Produkttabelle. Zeigt die Verwendung von BeforeEndpoint / AfterEndpoint zum Offnen und Schliessen der Datenbank.
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"
LOCAL loRes, lnRecs
SELECT * FROM productos INTO CURSOR tmpProductos
lnRecs = _TALLY
loRes = THIS.newObject("status,data,message")
loRes.status = "ok"
loRes.data = NULL
loRes.message = IIF(lnRecs = 0, "Keine Produkte gefunden", "")
IF lnRecs > 0
loRes = THIS.newObject(TEXTMERGE("status,data[<<lnRecs>>],message"))
LOCAL i
i = 0
SCAN
i = i + 1
SCATTER MEMO NAME loRow
loRes.data[i] = loRow
ENDSCAN
loRes.status = "ok"
loRes.message = ""
ENDIF
res.Status(200).Json(THIS.ToJson(loRes))
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 geloscht"
res.Status(200).Json(THIS.ToJson(loRes))
ENDPROC
ENDDEFINE JSON-Body fur POST/PUT
{
"nombre": "Mechanische Tastatur",
"precio": 89.99,
"stock": 50
} Tabellenstruktur fur 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.
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
ENDDEFINE JSON-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 fur das Offnen und Schliessen von Tabellen.
- Geben Sie passende HTTP-Codes zuruck: 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() fur 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 →