surql
The surql tagged template function creates parameterized SurrealQL queries with automatic value binding and SQL injection prevention.
Import:
import { surql } from 'surrealdb';
Source: utils/tagged-template.ts
Function Signature
function surql(
strings: TemplateStringsArray,
...values: unknown[]
): BoundQuery
Parameters
| Parameter | Type | Description |
|---|
strings required | TemplateStringsArray | Template string segments. |
values required | unknown[] | Interpolated values (automatically bound as parameters). |
Returns
BoundQuery - Parameterized query with automatic bindings
How It Works
The surql template automatically:
- Extracts interpolated values
- Generates unique parameter names
- Replaces values with parameter references
- Returns a
BoundQuery with query string and bindings
const age = 18;
const query = surql`SELECT * FROM users WHERE age > ${age}`;
Basic Examples
Simple Parameterized Query
import { surql } from 'surrealdb';
const minAge = 18;
const query = surql`SELECT * FROM users WHERE age >= ${minAge}`;
const [users] = await db.query(query).collect();
Multiple Parameters
const status = 'active';
const minAge = 18;
const tier = 'premium';
const query = surql`
SELECT * FROM users
WHERE status = ${status}
AND age >= ${minAge}
AND tier = ${tier}
`;
const [users] = await db.query(query).collect();
With Value Types
import { RecordId, DateTime, Duration } from 'surrealdb';
const userId = new RecordId('users', 'john');
const cutoffDate = DateTime.now().minus(Duration.parse('30d'));
const query = surql`
SELECT * FROM posts
WHERE author = ${userId}
AND created_at >= ${cutoffDate}
ORDER BY created_at DESC
`;
const [posts] = await db.query(query).collect();
Advanced Examples
Dynamic Query Building
function buildUserQuery(filters: {
status?: string;
minAge?: number;
tier?: string;
}) {
let query = surql`SELECT * FROM users WHERE 1=1`;
if (filters.status) {
query.append(surql` AND status = ${filters.status}`);
}
if (filters.minAge !== undefined) {
query.append(surql` AND age >= ${filters.minAge}`);
}
if (filters.tier) {
query.append(surql` AND tier = ${filters.tier}`);
}
return query;
}
const query = buildUserQuery({ status: 'active', minAge: 18 });
const [users] = await db.query(query).collect();
Multi-Statement Queries
const userId = new RecordId('users', 'john');
const postId = new RecordId('posts', '123');
const query = surql`
BEGIN TRANSACTION;
UPDATE ${userId} SET post_count += 1;
CREATE ${postId} SET
author = ${userId},
title = ${'My Post'},
content = ${'Post content here'},
created_at = time::now();
COMMIT TRANSACTION;
`;
await db.query(query).collect();
Combining with Expressions
import { expr, eq, gte } from 'surrealdb';
const condition = expr(and(
eq('verified', true),
gte('age', 18)
));
const tier = 'premium';
const query = surql`
SELECT * FROM users
WHERE ${condition}
AND tier = ${tier}
`;
const [users] = await db.query(query).collect();
Inserting Arrays
const users = [
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' }
];
const query = surql`INSERT INTO users ${users}`;
await db.query(query).collect();
Graph Traversal
const userId = new RecordId('users', 'john');
const query = surql`
SELECT
*,
->follows->users.* AS following,
<-follows<-users.* AS followers
FROM ${userId}
`;
const [result] = await db.query(query).collect();
console.log('Following:', result.following);
console.log('Followers:', result.followers);
Conditional Updates
const status = 'inactive';
const threshold = DateTime.now().minus(Duration.parse('90d'));
const query = surql`
UPDATE users
SET status = ${status}
WHERE active = false
AND last_login < ${threshold}
`;
const [updated] = await db.query(query).collect();
console.log(`Updated ${updated.length} users`);
Variable Definition
const minScore = 80;
const category = 'tech';
const query = surql`
LET $high_scorers = SELECT * FROM users WHERE score >= ${minScore};
LET $tech_users = SELECT * FROM users WHERE category = ${category};
RETURN {
high_scorers: $high_scorers,
tech_users: $tech_users,
intersection: SELECT * FROM $high_scorers WHERE category = ${category}
};
`;
const [result] = await db.query(query).collect();
Batch Operations
const recordIds = [
new RecordId('users', 'alice'),
new RecordId('users', 'bob'),
new RecordId('users', 'carol')
];
const query = surql`
SELECT * FROM [${recordIds[0]}, ${recordIds[1]}, ${recordIds[2]}]
`;
const [users] = await db.query(query).collect();
SQL Injection Prevention
The surql template prevents SQL injection by automatically parameterizing all values:
const userInput = "'; DROP TABLE users; --";
const query = surql`SELECT * FROM users WHERE name = ${userInput}`;
Best Practices
const userName = getUserInput();
const query = surql`SELECT * FROM users WHERE name = ${userName}`;
const query = `SELECT * FROM users WHERE name = '${userName}'`;
2. Use for Complex Queries
const query = surql`
SELECT *,
->purchased->products.* AS purchases,
<-manages<-departments.* AS departments
FROM ${userId}
WHERE active = ${true}
`;
const query = new BoundQuery(
'SELECT *, ->purchased->products.* AS purchases FROM $userId WHERE active = $active',
{ userId, active: true }
);
3. Leverage Type System
const recordId = new RecordId('users', 'john');
const datetime = DateTime.now();
const query = surql`
UPDATE ${recordId}
SET last_login = ${datetime}
`;
4. Build Queries Incrementally
let query = surql`SELECT * FROM products WHERE 1=1`;
if (minPrice) {
query.append(surql` AND price >= ${minPrice}`);
}
if (category) {
query.append(surql` AND category = ${category}`);
}
query.append(surql` ORDER BY created_at DESC LIMIT ${limit}`);
Common Pitfalls
1. Identifier Interpolation
const tableName = 'users';
const wrong = surql`SELECT * FROM ${tableName}`;
const table = new Table('users');
const correct = surql`SELECT * FROM ${table}`;
2. Field Names
const fieldName = 'age';
const wrong = surql`SELECT * FROM users WHERE ${fieldName} > 18`;
import { escapeIdent } from 'surrealdb';
const validated = escapeIdent(fieldName);
const correct = surql`SELECT * FROM users WHERE ${raw(validated)} > 18`;
See Also