BoundQuery<R>

The BoundQuery class represents a parameterized SurrealQL query with bound variables, providing safe query composition and preventing SQL injection.

Import:

import { BoundQuery } from 'surrealdb';

Source: utils/bound-query.ts

Type Parameters

  • R extends unknown[] - Array of result types for the query

Constructor

new BoundQuery(query?, bindings?)

Create a new bound query.

Syntax

new BoundQuery() // Empty query
new BoundQuery(boundQuery) // Clone existing
new BoundQuery(query, bindings?) // From string and bindings

Parameters

ParameterTypeDescription
query string | BoundQueryQuery string or existing BoundQuery to clone.
bindings Record<string, unknown>Parameter bindings.

Examples

// Empty query
const query = new BoundQuery();

// From string
const query = new BoundQuery('SELECT * FROM users');

// With bindings
const query = new BoundQuery(
'SELECT * FROM users WHERE age > $age',
{ age: 18 }
);

// Clone existing
const clone = new BoundQuery(existingQuery);

Properties

query

The query string with parameter placeholders.

Type: string

const query = new BoundQuery(
'SELECT * FROM users WHERE age > $age',
{ age: 18 }
);

console.log(query.query);
// 'SELECT * FROM users WHERE age > $age'

bindings

A copy of the parameter bindings.

Type: Record<string, unknown>

console.log(query.bindings);
// { age: 18 }

Methods

.append()

Append another query or string to this query.

Method Syntax

query.append(other)
query.append(queryString, bindings?)
query.append`template ${value}`

Parameters

ParameterTypeDescription
otherBoundQuery | string | TemplateStringsArrayQuery to append.
bindings Record<string, unknown>Bindings for the appended query.

Returns

this - Chainable

Examples

Append BoundQuery

const base = new BoundQuery('SELECT * FROM users WHERE 1=1');
const filter = new BoundQuery(' AND age > $age', { age: 18 });

base.append(filter);

Append String

const query = new BoundQuery('SELECT * FROM users');
query.append(' WHERE active = $active', { active: true });

Append with Template

const query = new BoundQuery('SELECT * FROM users');
const status = 'active';
query.append` WHERE status = ${status}`;

.bind()

Add or update a parameter binding.

Method Syntax

query.bind(key, value)

Parameters

ParameterTypeDescription
key stringParameter name (without $ prefix).
value unknownParameter value.

Returns

this - Chainable

Example

const query = new BoundQuery('SELECT * FROM users WHERE age > $age');
query.bind('age', 18);
query.bind('status', 'active');

const [users] = await db.query(query).collect();

.toString()

Get the query string.

Method Syntax

query.toString()

Returns

string - The query string

Complete Examples

Basic Parameterized Query

import { BoundQuery } from 'surrealdb';

const query = new BoundQuery(
'SELECT * FROM users WHERE age >= $minAge AND status = $status',
{
minAge: 18,
status: 'active'
}
);

const [users] = await db.query(query).collect();

Building Queries Incrementally

// Start with base query
const query = new BoundQuery('SELECT * FROM products WHERE 1=1');

// Add conditions dynamically
if (category) {
query.append(' AND category = $category', { category });
}

if (minPrice) {
query.append(' AND price >= $minPrice', { minPrice });
}

if (maxPrice) {
query.append(' AND price <= $maxPrice', { maxPrice });
}

query.append(' ORDER BY created_at DESC LIMIT $limit', { limit: 10 });

const [products] = await db.query(query).collect();

Query Builder Pattern

class QueryBuilder {
private query: BoundQuery;

constructor(table: string) {
this.query = new BoundQuery(`SELECT * FROM ${table} WHERE 1=1`);
}

where(field: string, value: unknown): this {
this.query.append(` AND ${field} = $${field}`, { [field]: value });
return this;
}

limit(count: number): this {
this.query.append(' LIMIT $limit', { limit: count });
return this;
}

build(): BoundQuery {
return this.query;
}
}

// Usage
const builder = new QueryBuilder('users');
const query = builder
.where('status', 'active')
.where('verified', true)
.limit(10)
.build();

const [users] = await db.query(query).collect();

Reusable Query Fragments

// Define reusable fragments
const activeFilter = new BoundQuery('status = $status', { status: 'active' });
const verifiedFilter = new BoundQuery('verified = $verified', { verified: true });

// Combine them
const query = new BoundQuery('SELECT * FROM users WHERE ');
query.append(activeFilter);
query.append(' AND ');
query.append(verifiedFilter);

const [users] = await db.query(query).collect();

Complex Multi-Statement Query

const userId = new RecordId('users', 'john');
const postData = {
title: 'My Post',
content: 'Content here'
};

const query = new BoundQuery();

query.append('BEGIN TRANSACTION;');

query.append(
'UPDATE $userId SET post_count += 1;',
{ userId }
);

query.append(
'CREATE posts SET author = $author, title = $title, content = $content;',
{
author: userId,
title: postData.title,
content: postData.content
}
);

query.append('COMMIT TRANSACTION;');

await db.query(query).collect();

Pagination Helper

function paginatedQuery(
table: string,
page: number,
pageSize: number,
filters?: Record<string, unknown>
): BoundQuery {
const query = new BoundQuery(`SELECT * FROM ${table} WHERE 1=1`);

if (filters) {
for (const [key, value] of Object.entries(filters)) {
query.append(` AND ${key} = $${key}`, { [key]: value });
}
}

const offset = (page - 1) * pageSize;
query.append(' START $offset LIMIT $limit', {
offset,
limit: pageSize
});

return query;
}

// Usage
const query = paginatedQuery('users', 2, 20, { status: 'active' });
const [users] = await db.query(query).collect();

Best Practices

1. Use surql Template Instead

For most cases, the surql template is easier:

// Good: surql template (recommended)
const query = surql`SELECT * FROM users WHERE age > ${age}`;

// Also good: BoundQuery (more manual)
const query = new BoundQuery(
'SELECT * FROM users WHERE age > $age',
{ age }
);

2. Validate Parameter Names

// Good: Consistent parameter naming
const query = new BoundQuery(
'SELECT * FROM users WHERE age > $age AND status = $status',
{ age: 18, status: 'active' }
);

// Avoid: Mismatched names
const query = new BoundQuery(
'SELECT * FROM users WHERE age > $minAge',
{ age: 18 } // Wrong key name
);

3. Use append() for Dynamic Queries

// Good: Incremental building
const query = new BoundQuery('SELECT * FROM users WHERE 1=1');
if (filter) {
query.append(' AND status = $status', { status: filter });
}

// Avoid: String concatenation
let queryStr = 'SELECT * FROM users WHERE 1=1';
if (filter) {
queryStr += ` AND status = '${filter}'`; // Unsafe!
}

See Also