Skip to content
Sign In

Concepts

Error handling

The JavaScript SDK provides specific error classes for handling different types of failures when interacting with SurrealDB.

The JavaScript SDK defines specific error classes for different failure scenarios. All SDK errors extend the base SurrealError class, making it easy to distinguish SDK errors from other JavaScript errors and to handle specific failure types with instanceof checks.

The error kinds, wire codes and structured shape behind these are documented once in Errors, which applies to every SDK and protocol.

Error class

Description

SurrealError

Base class for all SDK errors

ConnectionUnavailableError

Thrown when operating without an active connection

AuthenticationError

Thrown when authentication fails

ResponseError

Thrown when a database query returns an error

UnsupportedVersionError

Thrown when the SurrealDB version is incompatible

A complete list of error classes is available in the Errors API reference.

A query travels through two layers, and it is worth knowing which one a failure comes from.

The first is the request itself: a connection that is unavailable, a rejected sign-in, a query that will not parse. The second is the individual statements inside the query, which can fail while the request as a whole succeeds.

Awaiting a query collapses both into a rejection. It rejects on the first statement that fails, so a query whose earlier statements succeeded resolves to nothing at all: those results are lost along with the error.

Where the per-statement outcome matters, .responses() returns every statement instead of rejecting. Each entry carries a success flag, and a failing one carries an error with its kind.

import { Surreal, createRemoteEngines, ThrownError } from 'surrealdb';

const db = new Surreal({ engines: { ...createRemoteEngines() } });
await db.connect('http://localhost:8000');
await db.signin({ username: 'root', password: 'secret' });
await db.use({ namespace: 'test', database: 'test' });

// Awaiting the query rejects on the first statement that fails, so the
// result of the statement that succeeded is not returned.
try {
    await db.query("RETURN 1; THROW 'second'");
} catch (e) {
    if (e instanceof ThrownError) {
        console.log(`${e.kind}: ${e.message}`);
    }
}

// .responses() reports every statement instead of rejecting.
const responses = await db.query("RETURN 1; THROW 'second'").responses();
for (const [index, statement] of responses.entries()) {
    console.log(index, statement.success, statement.success ? statement.result : statement.error.kind);
}
Output
Thrown: An error occurred: second
0 true 1
1 false Thrown

Every server error carries a kind, and the SDK throws a dedicated class for each of the kinds below. The meaning of each kind is described in Errors. Match on the kind or the class rather than on the message text, which is free to change between releases.

KindError class
ValidationValidationError
ConfigurationConfigurationError
QueryQueryError
SerializationSerializationError
NotAllowedNotAllowedError
NotFoundNotFoundError
AlreadyExistsAlreadyExistsError
ThrownThrownError
InternalInternalError

Any kind without a dedicated class, including one added by a newer server, arrives as the base ServerError with its kind intact. Catching ServerError therefore stays correct as the server grows new kinds.

All errors thrown by the SDK are instances of SurrealError. You can use instanceof checks to catch SDK errors broadly, or target specific error classes for fine-grained handling.

import { SurrealError, AuthenticationError, ConnectionUnavailableError } from 'surrealdb';

try {
    await db.signin({ username: 'user', password: 'pass' });
} catch (error) {
    if (error instanceof AuthenticationError) {
        console.error('Invalid credentials');
    } else if (error instanceof ConnectionUnavailableError) {
        console.error('Not connected to a database');
    } else if (error instanceof SurrealError) {
        console.error('SDK error:', error.message);
    }
}

Connection errors occur when the SDK cannot establish or maintain a connection to the database. The most common are ConnectionUnavailableError (thrown when you attempt an operation without a connection) and HttpConnectionError (thrown when an HTTP request fails).

import { ConnectionUnavailableError, HttpConnectionError } from 'surrealdb';

try {
    await db.connect('ws://localhost:8000');
} catch (error) {
    if (error instanceof HttpConnectionError) {
        console.error(`HTTP ${error.status}: ${error.statusText}`);
    }
}

If you attempt to use an engine protocol that has not been registered, the SDK throws an UnsupportedEngineError with the name of the unsupported engine.

import { UnsupportedEngineError } from 'surrealdb';

try {
    await db.connect('mem://');
} catch (error) {
    if (error instanceof UnsupportedEngineError) {
        console.error(`Engine "${error.engine}" is not registered`);
    }
}

An AuthenticationError is thrown when a sign-in or sign-up attempt fails. A MissingNamespaceDatabaseError is thrown when you attempt an operation that requires a namespace and database without having selected one.

import { AuthenticationError, MissingNamespaceDatabaseError } from 'surrealdb';

try {
    await db.use({ namespace: 'main', database: 'main' });
    await db.signin({ username: 'admin', password: 'secret' });
} catch (error) {
    if (error instanceof MissingNamespaceDatabaseError) {
        console.error('No namespace or database selected');
    } else if (error instanceof AuthenticationError) {
        console.error('Authentication failed:', error.cause);
    }
}

When a SurrealQL query fails, the SDK throws a ResponseError containing the error code and message from the database.

import { ResponseError } from 'surrealdb';

try {
    await db.query('INVALID QUERY');
} catch (error) {
    if (error instanceof ResponseError) {
        console.error(`Database error [${error.code}]: ${error.message}`);
    }
}

The SDK performs a version check when connecting to a SurrealDB instance by default. If the connected version is outside the supported range, an UnsupportedVersionError is thrown. You can disable this check using the versionCheck option on .connect().

import { UnsupportedVersionError } from 'surrealdb';

try {
    await db.connect('ws://localhost:8000');
} catch (error) {
    if (error instanceof UnsupportedVersionError) {
        console.error(
            `Version ${error.version} is not supported. ` +
            `Requires >= ${error.minimum} and < ${error.maximum}`
        );
    }
}

Some features are only available with specific engines or SurrealDB versions. An UnsupportedFeatureError is thrown when a feature is not supported by the configured engine, while an UnavailableFeatureError is thrown when the connected SurrealDB version does not support it.

You can proactively check for feature support using the isFeatureSupported() method to avoid these errors entirely.

import { Features, UnsupportedFeatureError } from 'surrealdb';

if (db.isFeatureSupported(Features.LiveQueries)) {
    const live = await db.live(new Table('users'));
}

The SDK emits an error event for errors that occur outside of direct method calls, such as reconnection failures. You can subscribe to these events using the .subscribe() method.

import { ReconnectExhaustionError, UnexpectedConnectionError } from 'surrealdb';

db.subscribe('error', (error) => {
    if (error instanceof ReconnectExhaustionError) {
        console.error('All reconnection attempts failed');
    } else if (error instanceof UnexpectedConnectionError) {
        console.error('Connection error:', error.cause);
    }
});

For operations that may fail transiently, you can implement retry logic. Combine specific error checks with a retry loop to handle recoverable failures gracefully.

import { ConnectionUnavailableError, ResponseError } from 'surrealdb';

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            return await fn();
        } catch (error) {
            if (error instanceof ConnectionUnavailableError) {
                await db.connect('ws://localhost:8000');
                continue;
            }
            if (error instanceof ResponseError && attempt < maxRetries - 1) {
                continue;
            }
            throw error;
        }
    }
    throw new Error('Max retries exceeded');
}

const users = await withRetry(() => db.select(new Table('users')));

For the specific case of write conflicts under concurrent load, prefer the SDK's built-in .retry() over a hand-rolled loop - it applies exponential backoff and only replays work known to be safely retryable.

const [n] = await db
    .query<[number]>('UPDATE counter:c SET n += 1 RETURN n')
    .retry({ attempts: 3 })
    .collect();

Was this page helpful?