Skip to content

Custom APIs

Middleware

When you use DEFINE API, the THEN block is the handler that decides the HTTP-shaped response. Middleware is everything you run before that handler, such as timeouts, logging, auth checks, or tweaks to the outgoing payload. SurrealDB runs built-in helpers from the API functions package alongside your own fn:: functions, in the order you list them.

Custom middleware functions are ordinary user-defined functions. Each one receives:

  • The current request object (conventionally $req).

  • A next closure that continues the chain and eventually runs THEN when no middleware remains.

You can add extra parameters after those two;such values are supplied when you attach the function to DEFINE API (for example passing in time::now() to return the time at which a request was processed).

The function must return an object - that is the response object passed to the next middleware or returned to the client. Names like $req and $next are only conventions; what matters is the order of arguments and that you actually call $next($req) when you want the pipeline to proceed.

DEFINE API "/custom_response"
    FOR get
        THEN {
            {
                status: 200,
                body: {
                    num: 1
                }
            };
        };

Calling api::invoke("/custom_response") returns the body you would expect:

{
	body: {
		num: 1
	},
	context: {},
	headers: {},
	status: 200
};

This middleware calls $next($req) to obtain the handler's result, then bumps body.num by one before returning:

DEFINE FUNCTION fn::increment_num($req: object, $next: function) -> object {
    LET $res = $next($req);
    $res + { body: { num: $res.body.num + 1 } }
};

DEFINE API "/custom_response"
    FOR get
        MIDDLEWARE
            fn::increment_num()
        THEN {
            {
                status: 200,
                body: {
                    num: 1
                }
            };
        };

Now api::invoke("/custom_response") yields num: 2, showing that the outer middleware has reshaped what the caller sees.

{
	body: {
		num: 2
	},
	context: {},
	headers: {},
	status: 200
};

Order matters when adding middleware, so be sure to list outer effects first if they should wrap everything that follows. Here a timer middleware adds a timestamp into context, and increment still adjusts the body:

DEFINE FUNCTION fn::start_timer($req: object, $next: function, $called_at: datetime) -> object {
    LET $res = $next($req);
    $res + { context: { called_at: $called_at }}
};

DEFINE FUNCTION fn::increment_num($req: object, $next: function) -> object {
    LET $res = $next($req);
    $res + { body: { num: $res.body.num + 1 } }
};

DEFINE API "/custom_response"
    FOR get
        MIDDLEWARE
            fn::start_timer(time::now()),
            fn::increment_num()
        THEN {
            {
                status: 200,
                body: {
                    num: 1
                }
            };
        };

api::invoke("/custom_response");

Possible output:

{
	body: {
		num: 2
	},
	context: {
		called_at: d'2026-01-16T01:49:44.115351Z'
	},
	headers: {},
	status: 200
};

Was this page helpful?