Routing
FoxServer uses HELP comments in VFP methods to declare routes. There are no separate route configuration files: the declaration lives alongside the controller code.
HELP comment syntax
Each endpoint method must have a HELP comment in this format:
PROCEDURE NombreMetodo(req, res) HELP "METODO: ruta/path [public]" | Part | Description | Example |
|---|---|---|
| HTTP Method | GET, POST, PUT, PATCH, DELETE, HEAD | GET |
| Route | Path relative to the server prefix. Can contain {parameters}. | products/{id} |
| Visibility | public = no JWT required. Without this token = JWT mandatory. | public |
Supported HTTP methods
| Method | Description | Has body? | Route example |
|---|---|---|---|
GET | Retrieve data | No | GET: products public |
POST | Create resource | Yes | POST: products public |
PUT | Update (full) | Yes | PUT: products/{id} public |
PATCH | Update (partial) | Yes | PATCH: products/{id} public |
DELETE | Delete resource | No | DELETE: products/{id} public |
HEAD | Headers only | No | HEAD: products public |
URL parameters
* Single parameter
PROCEDURE GetProduct(req, res) HELP "GET: products/{id} public"
LOCAL lcId
lcId = req.params.id && /api/products/42 -> "42"
res.status(200).json(...)
ENDPROC
* Multiple parameters
PROCEDURE GetVariant(req, res) HELP "GET: products/{id}/variants/{vid} public"
LOCAL lcProductId, lcVariantId
lcProductId = req.params.id && /api/products/42/variants/black -> "42"
lcVariantId = req.params.vid && "black"
res.status(200).json(...)
ENDPROC Query string
Query string parameters (?key=value) are accessed via req.query:
PROCEDURE SearchProducts(req, res) HELP "GET: products/search public"
* GET /api/products/search?name=Widget&minprice=10&page=2
LOCAL lcName, lnMin, lnPage
lcName = req.query.name && "Widget"
lnMin = VAL(req.query.minprice) && 10
lnPage = IIF(EMPTY(req.query.page), 1, VAL(req.query.page))
res.status(200).json(...)
ENDPROC Request object — properties
| Property | Type | Description |
|---|---|---|
method | String | HTTP method (GET, POST, etc.) |
url | Uri | Full request URL |
path | String | Path without query string |
params | Object | URL parameters ({id} → params.id) |
query | Object | Query string parameters (?key=val) |
headers | Dict | HTTP headers (case-insensitive) |
body | String | Request body as plain text |
json | Object | Parsed JSON body (if Content-Type: application/json) |
contentType | String | Content-Type header value |
remoteIP | String | Client IP address |
Response object — methods
| Method | Signature | Description |
|---|---|---|
status(code) | res.status(200) | Sets the HTTP code. Returns res for chaining. |
json(jsonStr) | res.json('{"ok":true}') | Sends JSON body. Sets Content-Type: application/json. |
send(text) | res.send("Hello") | Sends plain text or HTML. |
header(key, val) | res.header("X-Id","123") | Adds or overwrites a response header. |
location(url) | res.location("/api/v2") | Sets the Location header (for redirects). |
sendFile(path, disp) | res.sendFile("C:\\rep.pdf","attachment") | Sends a file. disp: "inline" or "attachment". |
* Chaining example — multiple headers + JSON response
res.status(201)
.header("X-Resource-Id", lcNewId)
.header("Cache-Control", "no-cache")
.json(THIS.ToJson(loResponse)) Controller lifecycle
Each controller can define hooks that run before and after each endpoint:
DEFINE CLASS ProductsController AS ApiController OLEPUBLIC
FUNCTION BeforeEndpoint(req, res) AS BOOLEAN
SET DELETED ON
SET EXCLUSIVE OFF
USE data\products SHARED
RETURN .T. && .F. here would abort the request
ENDFUNC
PROCEDURE AfterEndpoint(req, res)
CLOSE DATABASES ALL
ENDPROC
PROCEDURE GetProducts(req, res) HELP "GET: products public"
&& Products table is already open (BeforeEndpoint)
&& It will be closed automatically (AfterEndpoint)
res.status(200).json(...)
ENDPROC
ENDDEFINE Public vs. protected routes
Visibility is controlled with the public keyword in the HELP comment:
* PUBLIC endpoint — no JWT needed
PROCEDURE GetProducts(req, res) HELP "GET: products public"
res.status(200).json(...)
ENDPROC
* PRIVATE endpoint — JWT required (omit "public")
PROCEDURE DeleteProduct(req, res) HELP "DELETE: products/{id}"
&& Client must send: Authorization: Bearer eyJhb...
res.status(200).json(...)
ENDPROC When JWT middleware is active, endpoints without public require the client to send the token in the Authorization: Bearer {token} header.
Next: Middleware →