surqlThe 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 surql( strings: TemplateStringsArray, ...values: unknown[] ): BoundQuery
| Parameter | Type | Description |
|---|---|---|
strings required | TemplateStringsArray | Template string segments. |
values required | unknown[] | Interpolated values (automatically bound as parameters). |
BoundQuery - Parameterized query with automatic bindings
The surql template automatically:
BoundQuery with query string and bindingsconst 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 }
import { surql } from 'surrealdb'; const minAge = 18; const query = surql`SELECT * FROM users WHERE age >= ${minAge}`; const [users] = await db.query(query).collect();
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();
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();
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();
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();
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();
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();
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);
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`);
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();
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();
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
// 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}'`;
// 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 } );
// 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
// 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}`);
// 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}`;
// 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`;