IR
irwinrodriguez.dev
Back to docs

Middleware

Middleware in FoxServer are components that process requests before or after they reach the controller. You can use the built-in ones (JWT, CORS, logging) or write your own.

JWT — Authentication

The JWT middleware protects private endpoints. Clients must send a valid token in the Authorization header.

Configuration in JSON:

"middleware": {
  "auth": {
    "enabled": true,
    "type": "jwt",
    "secret": "your-256-bit-secret-key-base64-encoded",
    "tokenexpirationseconds": 3600,
    "loginendpoint": "auth/login"
  }
}

Authentication flow:

  1. Client POSTs to the login endpoint with credentials.
  2. Controller validates credentials and generates a JWT token.
  3. Client stores the token and sends it on subsequent requests.
  4. Middleware intercepts the request, validates the token before reaching the controller.
  5. If the token is invalid or expired, middleware returns 401 automatically.

Login controller (generates the token):

PROCEDURE PostLogin(req, res) HELP "POST: auth/login public"
    LOCAL lcUser, lcPass, lcToken, loResp

    lcUser = req.json.username
    lcPass = req.json.password

    IF !THIS.ValidateCredentials(lcUser, lcPass)
        res.status(401).json('{"error":"Invalid credentials"}')
        RETURN
    ENDIF

    * GenerateJWT is a built-in ApiController method
    lcToken = THIS.GenerateJWT(lcUser)

    loResp = THIS.newObject("token,expiresin")
    loResp.token     = lcToken
    loResp.expiresin = 3600
    res.status(200).json(THIS.ToJson(loResp))
ENDPROC

Protected endpoint (no additional code needed):

* Omit "public" — JWT middleware runs automatically before this
PROCEDURE GetMyProfile(req, res) HELP "GET: profile"
    LOCAL lcUserId, loResp

    lcUserId = THIS.GetUserIdFromToken(req)

    loResp = THIS.newObject("id,name,email")
    loResp.id    = lcUserId
    loResp.name  = THIS.GetUserName(lcUserId)
    loResp.email = THIS.GetUserEmail(lcUserId)
    res.status(200).json(THIS.ToJson(loResp))
ENDPROC
# Request with valid token:
curl http://localhost:8080/api/profile \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

# Request without token → 401 Unauthorized (automatic)
curl http://localhost:8080/api/profile
# {"error":"Unauthorized","message":"Missing or invalid token"}

CORS — Cross-Origin

The CORS middleware automatically responds to OPTIONS preflight requests and adds the necessary headers to all responses.

{
  "allowedorigins": "https://myapp.com,https://admin.myapp.com",
  "allowedmethods": "GET,POST,PUT,DELETE,OPTIONS",
  "allowedheaders": "Content-Type,Authorization,X-Requested-With"
}
# Browser sends preflight:
OPTIONS /api/products HTTP/1.1
Origin: https://myapp.com
Access-Control-Request-Method: POST

# FoxServer responds automatically:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
OPTIONS/preflight requests are answered automatically by the middleware without reaching the controller.

Logging

Automatically logs requests, responses and errors to daily files.

  • Simple — one line per request
  • Detailed — full information including headers and body (useful in development)
  • JSON — structured format for consumption by monitoring tools

Sample output (Detailed format):

[2026-04-11 14:23:45.123] INFO  GET /api/v1/products?page=1
  Client     : 192.168.1.100
  Status     : 200 OK
  Duration   : 45ms
  Bytes out  : 2048

[2026-04-11 14:23:46.456] ERROR POST /api/v1/orders
  Client     : 192.168.1.100
  Status     : 500 Internal Server Error
  Error      : Database connection failed
  Duration   : 1250ms

Custom middleware (Hooks)

You can intercept the request/response cycle with project-level hooks:

* Root controller — hooks apply to all endpoints in this project
DEFINE CLASS AppController AS ApiController OLEPUBLIC

    * Runs before EVERY endpoint
    FUNCTION BeforeRequest(req, res) AS BOOLEAN
        * Example: block specific IPs
        IF req.remoteIP == "10.0.0.99"
            res.status(403).json('{"error":"Forbidden"}')
            RETURN .F.   && .F. = stop processing
        ENDIF

        * Open shared resources
        SET DELETED ON
        SET EXCLUSIVE OFF

        RETURN .T.   && .T. = continue to controller
    ENDFUNC

    * Runs after EVERY response is sent
    PROCEDURE AfterResponse(req, res)
        * Cleanup, metrics, audit log
        THIS.LogAudit(req.method, req.path, res.statusCode, req.remoteIP)
    ENDPROC

ENDDEFINE
Hooks are special methods in the root controller. Returning .F. in BeforeRequest aborts the request and returns 403 automatically.

Next: Licensing →