SurrealDB
SurrealDB Docs Logo

Enter a search query

Navigation

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

ParameterTypeDescription
strings requiredTemplateStringsArrayTemplate string segments.
values requiredunknown[]Interpolated values (automatically bound as parameters).

Returns

BoundQuery - Parameterized query with automatic bindings

How It Works

The surql template automatically:

  1. Extracts interpolated values
  2. Generates unique parameter names
  3. Replaces values with parameter references
  4. Returns a BoundQuery with query string and bindings
const age = 18; const query = surql`SELECT * FROM users WHERE age > ${age}`; // Internally becomes: // query.query = "SELECT * FROM users WHERE age > $bind__1" // query.bindings = { bind__1: 18 }

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:

// User input const userInput = "'; DROP TABLE users; --"; // Safe: Treated as a parameter value const query = surql`SELECT * FROM users WHERE name = ${userInput}`; // Becomes: SELECT * FROM users WHERE name = $bind__1 // With binding: { bind__1: "'; DROP TABLE users; --" } // The malicious SQL is safely treated as a string value

Best Practices

1. Always Use surql for User Input

// Good: Safe parameterization const userName = getUserInput(); const query = surql`SELECT * FROM users WHERE name = ${userName}`; // Dangerous: SQL injection risk const query = `SELECT * FROM users WHERE name = '${userName}'`;

2. Use for Complex Queries

// Good: Clear and safe const query = surql` SELECT *, ->purchased->products.* AS purchases, <-manages<-departments.* AS departments FROM ${userId} WHERE active = ${true} `; // Harder to read and maintain const query = new BoundQuery( 'SELECT *, ->purchased->products.* AS purchases FROM $userId WHERE active = $active', { userId, active: true } );

3. Leverage Type System

// Good: Type-safe values const recordId = new RecordId('users', 'john'); const datetime = DateTime.now(); const query = surql` UPDATE ${recordId} SET last_login = ${datetime} `; // Values maintain their types through the query

4. Build Queries Incrementally

// Good: Append for dynamic queries 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

// Problem: Table names can't be parameterized const tableName = 'users'; const wrong = surql`SELECT * FROM ${tableName}`; // Creates $bind__1 // Solution: Use Table class const table = new Table('users'); const correct = surql`SELECT * FROM ${table}`;

2. Field Names

// Problem: Field names as parameters const fieldName = 'age'; const wrong = surql`SELECT * FROM users WHERE ${fieldName} > 18`; // Solution: Use raw SQL for field names (with validation) import { escapeIdent } from 'surrealdb'; const validated = escapeIdent(fieldName); const correct = surql`SELECT * FROM users WHERE ${raw(validated)} > 18`;

See Also