Available since: v2.2.0
CautionCurrently, this is an experimental feature as such, it may be subject to breaking changes and may present unidentified security issues. Do not rely on this feature in production applications. To enable this, set the
SURREAL_CAPS_ALLOW_EXPERIMENTAL
environment variable todefine_api
.
The DEFINE API
statements allows a custom endpoint to be created. Each endpoint created by a DEFINE API
statement is located at the /api/:namespace/:database/:endpoint_name
path. For example, an endpoint for the path get_users
for the namespace my_namespace
and database my_database
will have the path /api/my_namespace/my_database/get_users
.
The response is an object with a combination of the following properties:
raw
- A boolean that when set to true
sends the response back as raw bytes or a string.status
- A valid HTTP status code.headers
- An object of valid header key value pairs.body
- A SurrealQL value, automatically encoded back into how the user requested it. When raw
is set to true
, this needs to be a string or bytes value.
SurrealQL SyntaxDEFINE API [ OVERWRITE | IF NOT EXISTS ] @endpoint [ FOR @HTTP_method, .. ] [ MIDDLEWARE @function, .. ] [ THEN { @value } ] [ PERMISSIONS [ NONE | FULL | @expression ]
DEFINE API
is often used in conjunction with a capabilities flag or environment variable to disable arbitrary queries, thereby forcing record and anonymous users to interact with the database via API endpoints alone.
Defining an API endpointDEFINE API "/test" FOR get, post MIDDLEWARE api::req::raw_body(false) THEN { RETURN { status: 200, body: { request: $request.body, response: "The server works" }, headers: { 'last-modified': time::now(), 'expires': time::now() + 4d } }; };
An API endpoint can be tested using the api::invoke
function, which takes either the path as a single string or the path along with a request body. It can also be tested via CURL or other means by directly using the endpoint along with the namespace and database in the headers.
api::invoke("/test"); api::invoke("/test", { body: { hi: "please", give: "me", the: "information" } });
Output-------- Query -------- { body: { request: NONE, response: 'The server works' }, headers: { "access-control-allow-origin": '*', expires: '2025-02-24T02:43:50.137321Z', "last-modified": '2025-02-20T02:43:50.137326Z' }, raw: false, status: 200 } -------- Query -------- { body: { request: { give: 'me', hi: 'please', the: 'information' }, response: 'The server works' }, headers: { "access-control-allow-origin": '*', expires: '2025-02-24T02:43:50.137455Z', "last-modified": '2025-02-20T02:43:50.137457Z' }, raw: false, status: 200 }
The path of a DEFINE API
statement can be static, such as "/test"
, dynamic, or the remainder of a URL.
A dynamic path uses a :
(colon) followed by a name, which will match on anything passed in at that section of a path.
DEFINE API OVERWRITE "/test/:anything_goes" FOR get THEN { RETURN { body: { some: "data" } } }; api::invoke("/test/this_matches"); api::invoke("/test/same_here"); api::invoke("/test/but/this/wont/match");
The first two api::invoke
calls return the output below, but the third returns nothing as :anything_goes
only applies to a single path segment.
Output{ body: { some: 'data' }, headers: {}, raw: false, status: 200 }
To match on the remainder of a URL, change the :
(colon) to a *
(star).
DEFINE API OVERWRITE "/test/*anything_goes" FOR get THEN { RETURN { body: { some: "data" } } }; api::invoke("/test/this_matches"); api::invoke("/test/same_here"); api::invoke("/test/works/with/multiple/paths/now");
All three api::invoke
calls will now show the following output.
Output{ body: { some: 'data' }, headers: {}, raw: false, status: 200 }