IR
irwinrodriguez.dev
Zuruck zur Dokumentation

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 →