SurrealDB Docs Logo

Enter a search query

DEFINE API statement

Available since: v2.2.0

Caution

Currently, 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 to define_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.

Statement syntax

SurrealQL Syntax
DEFINE 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.

Quick example

Defining an API endpoint
DEFINE 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 }

API paths

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 }