This package provides utilities for building SQL query strings in a safe, composable, and parameterized manner. It is designed to prevent SQL injection vulnerabilities and enhance code readability when constructing complex queries.
Primary author: Alex Corrado
npm install sql-string-builderAt the heart of this package are two main concepts:
QueryStringBuilder: A class that allows you to progressively build SQL queries by appending fragments and parameterized values.sqltagged template literal: A function that simplifies the creation ofQueryStringBuilderinstances, enabling a more natural and readable way to write SQL queries within JavaScript/TypeScript code.
Additionally, the package offers UnsafeQueryLiteral for situations where direct string interpolation is necessary (with strong warnings) and helper functions for common SQL construction patterns.
import { sql } from "sql-string-builder";The sql tagged template literal is the most convenient and recommended way to create QueryStringBuilder instances. It allows you to write SQL queries in a template literal syntax, embedding JavaScript/TypeScript expressions as parameterized values.
Usage:
const query = sql`SQL query text with ${parameter1} and ${parameter2}`;- The template literal is tagged with
sql. - Placeholders
${expression}are used to embed JavaScript/TypeScript expressions. These expressions will be treated as parameterized values. - The
sqlfunction returns aQueryStringBuilderinstance.
Example:
import { sql } from "sql-string-builder";
const productName = "Awesome Gadget";
const price = 99.99;
const query = sql`
SELECT *
FROM products
WHERE name = ${productName}
AND price <= ${price}
`;
const [queryText, values] = query.build();
console.log(queryText); // Output: SELECT * FROM products WHERE name = $1 AND price <= $2
console.log(values); // Output: [ 'Awesome Gadget', 99.99 ]import { UnsafeQueryLiteral } from "sql-string-builder";UnsafeQueryLiteral is a special type that wraps a string. When a QueryStringBuilder encounters an UnsafeQueryLiteral, it directly interpolates the string value into the SQL query without parameterization or escaping.
UnsafeQueryLiteral should ONLY be used for parts of the SQL query that are absolutely guaranteed to be safe and not derived from user input or any untrusted source. Using UnsafeQueryLiteral with user-controlled strings creates a severe SQL injection vulnerability.
When to (Rarely) Use UnsafeQueryLiteral:
- For injecting static, hardcoded SQL keywords, identifiers (table names, column names), or functions that are known to be safe.
- In scenarios where parameterization is not possible for a specific part of the SQL syntax (e.g., table or column names in some database systems).
Example (Use with Caution - for illustration only):
import { sql, UnsafeQueryLiteral } from "sql-string-builder";
const tableName = new UnsafeQueryLiteral("users_table"); // Static, known table name
const columnName = new UnsafeQueryLiteral("username"); // Static, known column name
const query = sql`SELECT ${columnName} FROM ${tableName} WHERE id = ${123}`;
const [queryText, values] = query.build();
console.log(queryText); // Output: SELECT username FROM users_table WHERE id = $1
console.log(values); // Output: [ 123 ]In most cases, you should avoid UnsafeQueryLiteral and rely on parameterized values and the QueryStringBuilder to construct your SQL queries safely.
import { QueryStringBuilder, sql } from "sql-string-builder";QueryStringBuilder is the primary class in this package for constructing SQL queries. It provides a fluent interface for appending SQL fragments and parameterized values.
constructor(
tokenForIndex: (i: number) => string,
queryTexts?: string[],
values?: readonly any[]
)While you can directly instantiate QueryStringBuilder, it is generally recommended to use the sql tagged template literal for easier creation.
Parameters (for direct instantiation - usually not needed):
tokenForIndex: (i: number) => string: A function that, given an indexi, returns the placeholder token for the i-th value. For PostgreSQL, this is typically(i) => "$" + (i + 1).queryTexts?: string[]: An array of string fragments that form the base SQL query.values?: readonly any[]: An optional array of values to be parameterized into the query.
Appends another QueryStringBuilder to the current builder. This is the core method for composing larger queries from smaller, reusable parts.
Parameters:
part: QueryStringBuilder: TheQueryStringBuilderto append.
Returns:
QueryStringBuilder: Returnsthis(the currentQueryStringBuilder) for method chaining.
Example:
import { sql } from "sql-string-builder";
const selectClause = sql`SELECT * FROM users`;
const whereClause = sql`WHERE age > ${18}`;
const query = selectClause.append(whereClause);
const [queryText, values] = query.build();
console.log(queryText); // Output: SELECT * FROM usersWHERE age > $1
console.log(values); // Output: [ 18 ]Appends a raw string directly to the SQL query.
Similar to UnsafeQueryLiteral, appendRawString bypasses parameterization and directly injects the provided string into the SQL query. This should only be used for safe, trusted, and non-user-controlled parts of the query.
Parameters:
part: string: The raw string to append.
Returns:
QueryStringBuilder: Returnsthisfor method chaining.
Example (Use with Caution - for illustration only):
import { sql } from "sql-string-builder";
const orderByClause = sql`ORDER BY created_at`;
const direction = "DESC"; // Static, known direction
const query = orderByClause.appendRawString(` ${direction}`);
const [queryText, values] = query.build();
console.log(queryText); // Output: ORDER BY created_at DESC
console.log(values); // Output: []Creates a new, mutable QueryStringBuilder that is a copy of the current builder. This is useful when you need to modify a query without affecting the original builder.
Returns:
QueryStringBuilder: A newQueryStringBuilderinstance with the same content as the original.
Example:
import { sql } from "sql-string-builder";
const baseQuery = sql`SELECT * FROM products`;
const query1 = baseQuery.clone().append(sql` WHERE price < ${100}`);
const query2 = baseQuery
.clone()
.append(sql` WHERE category = ${"Electronics"}`);
const [queryText1, values1] = query1.build();
console.log(queryText1); // Output: SELECT * FROM products WHERE price < $1
console.log(values1); // Output: [ 100 ]
const [queryText2, values2] = query2.build();
console.log(queryText2); // Output: SELECT * FROM products WHERE category = $1
console.log(values2); // Output: [ 'Electronics' ]
// baseQuery remains unchanged
const [baseQueryText, baseQueryValues] = baseQuery.build();
console.log(baseQueryText); // Output: SELECT * FROM products
console.log(baseQueryValues); // Output: []Finalizes the QueryStringBuilder and generates the SQL query string and an array of parameterized values. After calling build(), the QueryStringBuilder instance becomes frozen, and further modifications will result in errors.
Returns:
[queryText: string, values: any[] | undefined]: An array containing two elements:queryText: string: The complete SQL query string with parameter placeholders (e.g.,$1,$2, etc.).values: any[] | undefined: An array of values that correspond to the parameter placeholders inqueryText. This array will beundefinedif there are no parameterized values.
Example:
import { sql } from "sql-string-builder";
const name = "John Doe";
const age = 30;
const query = sql`INSERT INTO users (name, age) VALUES (${name}, ${age})`;
const [queryText, values] = query.build();
console.log(queryText); // Output: INSERT INTO users (name, age) VALUES ($1, $2)
console.log(values); // Output: [ 'John Doe', 30 ]
// You can now execute the queryText and values with your database client.Returns an approximate length of the SQL query string being built. This can be useful for performance optimizations or logging purposes.
Returns:
number: An approximate length of the SQL query string.
Example:
import { sql } from "sql-string-builder";
const longQuery = sql``;
for (let i = 0; i < 100; i++) {
longQuery.append(sql`SELECT * FROM table${i}; `);
}
console.log(longQuery.approximateLength()); // Output: A number representing the approximate lengthThe sql-string-builder package provides several helper functions to simplify common SQL construction tasks.
Joins an array of QueryStringBuilder instances into a single QueryStringBuilder, using the specified separator string between each item.
Parameters:
items: readonly QueryStringBuilder[]: An array ofQueryStringBuilderinstances to join.separator: string: The string to use as a separator between the joined items.
Returns:
QueryStringBuilder: A newQueryStringBuilderrepresenting the joined SQL fragments.
Example:
import { sql, joinSQL } from "sql-string-builder";
const conditions = [
sql`age > ${18}`,
sql`city = ${"New York"}`,
sql`is_active = ${true}`,
];
const whereClause = sql`WHERE `.append(joinSQL(conditions, " AND "));
const query = sql`SELECT * FROM users `.append(whereClause);
const [queryText, values] = query.build();
console.log(queryText); // Output: SELECT * FROM users WHERE age > $1 AND city = $2 AND is_active = $3
console.log(values); // Output: [ 18, 'New York', true ]A convenience function that joins an array of QueryStringBuilder instances using a comma and a space (, ) as the separator. This is commonly used for constructing comma-separated lists in SQL (e.g., in SELECT or INSERT statements).
Parameters:
items: readonly QueryStringBuilder[]: An array ofQueryStringBuilderinstances to join.
Returns:
QueryStringBuilder: A newQueryStringBuilderrepresenting the comma-separated SQL fragments.
Example:
import { sql, commaJoinSQL } from "sql-string-builder";
const columns = [sql`name`, sql`email`, sql`created_at`];
const selectClause = sql`SELECT `
.append(commaJoinSQL(columns))
.append(sql` FROM users`);
const [queryText, values] = selectClause.build();
console.log(queryText); // Output: SELECT name, email, created_at FROM users
console.log(values); // Output: []A helper function that takes an array of strings and converts them into a comma-separated list of parameterized values within a QueryStringBuilder.
Parameters:
strings: readonly string[]: An array of strings to be joined and parameterized.
Returns:
QueryStringBuilder: A newQueryStringBuilderrepresenting the comma-separated parameterized string values.
Example:
import { sql, commaJoinStringsToSQL } from "sql-string-builder";
const userEmails = [
"user1@example.com",
"user2@example.com",
"user3@example.com",
];
const whereClause = sql`WHERE email IN (`
.append(commaJoinStringsToSQL(userEmails))
.append(sql`)`);
const query = sql`SELECT * FROM users `.append(whereClause);
const [queryText, values] = query.build();
console.log(queryText); // Output: SELECT * FROM users WHERE email IN ($1, $2, $3)
console.log(values); // Output: [ 'user1@example.com', 'user2@example.com', 'user3@example.com' ]- SQL Injection Prevention: This package is designed to help prevent SQL injection vulnerabilities by promoting the use of parameterized queries. Always use parameterized values (using
${expression}within thesqltemplate literal) for any data that originates from user input or untrusted sources. UnsafeQueryLiteralandappendRawString: These features should be used with extreme caution and only when absolutely necessary for static, trusted parts of the SQL query. Improper use can reintroduce SQL injection risks. Thoroughly review and understand the security implications before using them.
By using the sql-string-builder package correctly, you can build robust, readable, and secure SQL queries in your applications.
MIT