NEW

The wait is over. SurrealDB 3.0 is here!

Learn more

Custom API Endpoints: streamlining your architecture

Featured
Engineering

Feb 18, 20264 min read

SurrealDB

SurrealDB

Show all posts

Custom API Endpoints: streamlining your architecture

Our newsletter

Get tutorials, AI agent recipes, webinars, and early product updates in your inbox every two weeks

With the release of SurrealDB 3.0, we’re excited to announce the stabilisation of a powerful feature: DEFINE API. This innovative addition allows developers to define database behaviours, set up middleware, and create custom API endpoints directly within the familiar SurrealQL query language.

Traditionally, applications connect to databases through middleware, resulting in a three-layer architecture:

  • Client → Middleware (API) → Database

With SurrealDB's new DEFINE API, you can simplify your infrastructure significantly:

  • Client → Database

This streamlined approach not only reduces complexity but also enhances performance and ease of management.

Real-world use case: implementing rate limits for a social app

Consider a scenario: you've built a popular social application that allows users to see recent comments. You want to provide a free API for anonymous users, but you also need to ensure these guest users don't overload your system resources during peak traffic.

Here are some ways how you can achieve this efficiently using SurrealDB's DEFINE API:

Setup: restrict arbitrary queries

To begin, pass in the new --deny-arbitrary-query flag when starting the SurrealDB server.

surreal start --user root --pass root --deny-arbitrary-query guest --allow-experimental define_api

This prevents certain groups, like guests or record users, from putting their own queries together. We'll add guest here so that any guest users will have to use the API endpoint instead.

You can then connect using Surrealist, or with the following command in a new terminal window:

surreal sql --user root --pass root

Defining your API Endpoint

Next, we'll add a DEFINE API statement to define the endpoint. The statement will start with the path /get_latest , and to specify that it can only be used for GET requests, we will include it after the FOR keyword.

DEFINE API OVERWRITE "/get_latest" FOR get

Add middleware functions

We can follow this up with some middleware by passing in some of SurrealDB's API functions. We'll use api::timeout to give guest users up to 50 milliseconds of server time.

MIDDLEWARE
api::timeout(50ms)

Define the RETURN data

And then we'll finish up the statement by adding what it actually returns: a SELECT statement for all comment records that have been created over the past ten minutes:

THEN {
{
body: SELECT * FROM comment:[time::now()-10m].. ORDER BY id DESC
}
};

In summary, that gives us a DEFINE API statement that looks like this.

DEFINE API OVERWRITE "/get_latest" FOR get
MIDDLEWARE
api::timeout(50ms),
THEN {
{
body: SELECT * FROM comment:[time::now()-10m].. ORDER BY id DESC
}
};

Testing your endpoint

You can test it from SurrealQL itself by first adding a comment or two and then calling the api::invoke function:

CREATE comment:[time::now()] SET user_says = "Nice blog!";
CREATE comment:[time::now()] SET user_says = "Can't wait for the new version";

api::invoke("/get_latest");

Or externally at: /api/:namespace/:database/:endpoint_name. Here's what our defined endpoint will look like with a namespace called test_ns and a database called test_db.

http://localhost:8000/api/test_ns/test_db/get_latest

That means that a curl command like the following will be enough to get the latest comments.

curl -H "Accept: application/json" \
http://localhost:8000/api/test_ns/test_db/get_latest

The output should look something like this.

[
{"id":"comment:[d'2026-02-12T01:10:02.445607Z']","user_says":"Can't wait for the new version"},
{"id":"comment:[d'2026-02-12T01:08:33.709073Z']","user_says":"Nice blog!"}
]

But that’s not all. The MIDDLEWARE clause in a DEFINE API statement can take custom middleware as well that you define yourself using a regular DEFINE FUNCTION statement. These functions are automatically populated with the user request and a way to get the current state of the response as you put it together.

There are a lot of examples in the documentation to show how it works, but here’s a simple example to get you started.

DEFINE FUNCTION fn::add_prefix($req: object, $next: function, $prefix: string) -> object {
LET $res = $next($req);
LET $res = $res + { body: $res.body + { prefix: $prefix + ": " + $res.body.message } };
$res;
};

DEFINE API "/custom_with_args"
FOR get
MIDDLEWARE
fn::add_prefix("PREFIX")
THEN {
{
status: 200,
body: {
message: "original message"
}
};
};

With DEFINE API, SurrealDB brings the power of API design directly into the database - no external layers, no additional frameworks. Whether you’re enforcing limits, defining access, or building powerful custom endpoints, this feature helps you simplify your architecture and move faster.

Explore further

Dive deeper into SurrealDB's powerful API management capabilities:

Harness the power of DEFINE API and redefine what's possible with SurrealDB 3.0.

Our newsletter

Get tutorials, AI agent recipes, webinars, and early product updates in your inbox every two weeks