SDK .query() with Parameters
SDK .query() with Parameters
Level 10 — SDKs, Deployment & Production Executing raw SurrealQL query strings through client SDKs using parameterized variable maps (
$param) for complex queries, transactions, and multi-statement execution.
1. Prerequisites
- JavaScript / TypeScript SDK — The
surrealdbclient package. - SurrealQL Injection Prevention — Parameterized query security.
- Parameters (
$param) — Parameter binding syntax. - SDK Connection Lifecycle (
connect/use/signin/close) — Executing raw SurrealQL queries via SDKs.
2. Term Category
Integration / Ecosystem (SDK raw SurrealQL query execution & parameter binding): - SDK Methods & Querying
3. Explanation
(1) Design Motivation — "Why did we design this?"
While high-level CRUD methods (.select(), .create()) handle simple single-table operations, real-world applications frequently require advanced SurrealQL features: graph traversals (->wrote->post), record link fetching (FETCH), aggregation (GROUP BY), conditional blocks (IF/ELSE), or multi-statement transactions (BEGIN ... COMMIT).
The SDK .query() method allows developers to execute arbitrary raw SurrealQL strings. To maintain security, .query() accepts a second parameter: a key-value object containing query variables. SurrealDB binds these variables safely on the server, guaranteeing complete immunity against SurrealQL injection attacks.
(2) Reality Metaphor
Think of an online banking form:
- You fill out a pre-structured transfer template by supplying inputs in designated fields (
Account Number,Amount). The banking backend treats your input strictly as data parameters, refusing to process any arbitrary malicious system instructions pasted into theAmountbox.
(3) Code Examples
Short Snippet
// Executing a raw SurrealQL query with parameterized bindings
const [results] = await db.query<[User[]]>(
'SELECT * FROM user WHERE age >= $min_age AND active = $status',
{ min_age: 21, status: true }
);
Fuller Example
import { Surreal, RecordId } from 'surrealdb';
interface PostDetails {
id: RecordId<'post'>;
title: string;
author: {
id: RecordId<'user'>;
name: string;
};
}
async function fetchUserPosts(db: Surreal, authorId: RecordId<'user'>) {
// 1. Raw SurrealQL string with FETCH and parameter binding
const query = `
SELECT
id,
title,
author
FROM post
WHERE author = $author_id AND published = true
FETCH author;
`;
// 2. Execute query safely with variables map
const [posts] = await db.query<[PostDetails[]]>(query, {
author_id: authorId
});
console.log('Fetched author posts with inline resolved data:', posts);
return posts;
}
4. Common Mistakes & Pitfalls
Mistake 1: Forgetting that .query() Returns an Array of Result Sets
The mistake: Expecting await db.query(...) to return a single array of records directly.
Why it's wrong: A SurrealQL query string can contain multiple statements separated by semicolons (SELECT * FROM a; SELECT * FROM b;). Therefore, .query() returns an array of result objects (one per statement). Destructuring const [result] = await db.query(...) extracts the first statement's result.
Incorrect:
// Error! db.query returns [ [records] ], not [records]
const users = await db.query('SELECT * FROM user');
console.log(users.map(u => u.name)); // TypeError!
Fix:
// Destructure the first statement result set
const [users] = await db.query<[User[]]>('SELECT * FROM user');
console.log(users.map(u => u.name)); // Works!
Mistake 2: Concatenating Untrusted Client Variables into db.query() Strings
The mistake: Calling db.query('SELECT * FROM user WHERE name = "' + name + '"').
Why it's wrong: String concatenation introduces SQL injection vulnerabilities. Always pass parameters using $var placeholders and parameter objects.
Incorrect:
await db.query(`SELECT * FROM user WHERE name = '${input}'`); // ❌ Injection risk!
Fix:
await db.query('SELECT * FROM user WHERE name = $name', { name: input }); // Safe parameter binding
Mistake 3: Expecting db.query() to Return a Single Unwrapped Record Result directly
The mistake: Expecting const user = await db.query('SELECT * FROM user:alice') to return { id: user:alice }.
Why it's wrong: db.query() returns an array of response objects [{ status: 'OK', result: [...] }]. Un-array the batch response array.
Incorrect:
const user = await db.query('SELECT * FROM user:alice'); // ❌ Returns response array, not user object!
Fix:
const [res] = await db.query('SELECT * FROM user:alice'); const user = res.result[0];
5. Practice Exercises
Exercise 1: Raw SurrealQL Execution with db.query()
Scenario:
Execute a multi-statement raw SurrealQL script containing parameter bindings using db.query().
Requirements:
- Execute
db.query()passing SurrealQL string and parameter map.
Answer
Implementation
const [users, orders] = await db.query<[User[], Order[]]>(
"SELECT * FROM user WHERE active = $active; SELECT * FROM order;",
{ active: true }
);
console.log("Active users count:", users.length);
Technical Explanation
db.query(surrealql, params)executes raw multi-statement SurrealQL scripts.- Parameter map (
{ active: true }) binds parameter variables safely, preventing SQL injection. - Returns a tuple array containing results of each statement in order.
Exercise 2: Type-Safe Tuple Result Destructuring
Scenario: Destructure multi-statement query results into strongly-typed TypeScript array variables.
Requirements:
- Type output tuple array
[User[], Product[]].
Answer
Implementation
const [userList, productList] = await db.query<[User[], Product[]]>(`
SELECT * FROM user;
SELECT * FROM product;
`);
Technical Explanation
- Generic tuple types (
<[User[], Product[]]>) enforce strict typing on multi-statement returns. - Unpacks array results cleanly.
- Combines raw query flexibility with TypeScript type safety.
Exercise 3: Passing Dynamic Parameters to db.query()
Scenario:
Pass complex parameters (like record links and datetimes) safely to db.query().
Requirements:
- Pass
{ targetUser: "user:alice", minDate: new Date() }.
Answer
Implementation
const results = await db.query(
"SELECT * FROM post WHERE author = $targetUser AND created_at > $minDate;",
{
targetUser: "user:alice",
minDate: new Date().toISOString()
}
);
Technical Explanation
- Parameter maps accept strings, numbers, booleans, arrays, and ISO date strings.
- Encodes parameter types safely before transmission over WebSockets.
- Provides secure parameter binding for dynamic queries.
6. Related Terms
- SurrealQL Injection Prevention — Security protections.
- SDK CRUD Methods (
.select()/.create()/.update()/.delete()) — High-level CRUD alternative. - Parameters (
$param) — Parameter syntax details.
7. Key Takeaways
db.query()executes raw SurrealQL query strings with variable parameter maps.- Always use the second parameter object
{ key: value }for dynamic input to prevent injection attacks. - Destructure return values (
const [result] = await db.query(...)) to access individual statement result sets.