@nearform/sql
@nearform/sql is a tagged-template library that turns ES6 template literals into
parameterized queries, immune to SQL injection by construction. It works with
pg (PostgreSQL), mysql, and mysql2. You build a query with the SQL`...`
tag and pass the resulting statement object straight to your driver.
When to use this skill
Reach for this skill whenever you:
- Write or review code that builds SQL queries in Node.js.
- See a raw SQL string assembled with
+,${...}inside a plain template string, orstring.replace— that is a SQL-injection red flag; rewrite it with theSQL`` tag. - Need dynamic queries: conditional
WHERE, dynamicSET, bulkINSERT, orIN (...)lists. - Interpolate identifiers (table/column names) into a query.
For the full API reference see
references/api-cheatsheet.md. For framework integration and dynamic-query recipes seereferences/patterns.md.
Install & import
npm install @nearform/sql
import SQL from '@nearform/sql' // ESM — prefer this for new code
const SQL = require('@nearform/sql') // CommonJS — also supported
The package itself is published as CommonJS (no exports map), but it interops
cleanly with ESM through a default import — use import SQL from '@nearform/sql',
then call SQL.glue, SQL.map, etc. off that default.
Statements are fully typed — the package ships SQL.d.ts, and helpers like
SQL.map<T>(array, mapFunc?) are generic, so TypeScript infers the element type.
The golden rule
Every runtime value goes through ${...} inside the SQL `` tag. The tag captures
each interpolated value as a bound parameter — it never injects it into the query text.
const username = "Robert'); DROP TABLE students;--"
const sql = SQL`SELECT * FROM users WHERE username = ${username}`
// sql.text -> "SELECT * FROM users WHERE username = $1" (pg)
// sql.sql -> "SELECT * FROM users WHERE username = ?" (mysql)
// sql.values -> ["Robert'); DROP TABLE students;--"] (bound, harmless)
- Never build the query with string concatenation or an untagged template literal.
SQL.unsafe(value)andappend(..., { unsafe: true })interpolate literally and bypass this protection — use them only for values you fully control, never for user input. See the cheatsheet.
Core usage
The statement object is driver-agnostic. Choose the property your client expects, or
pass the whole object — pg, mysql, and mysql2 all read what they need from it.
const user = { username: 'alice', email: 'alice@example.com' }
const sql = SQL`
INSERT INTO users (username, email)
VALUES (${user.username}, ${user.email})
`
// PostgreSQL (pg) — uses sql.text ($1, $2) + sql.values
await pgClient.query(sql)
// MySQL (mysql / mysql2) — uses sql.sql (?, ?) + sql.values
mysqlConnection.query(sql)
| Property | Use it for | Placeholder style |
|---|---|---|
sql.text |
PostgreSQL (pg) |
$1, $2, … |
sql.sql |
MySQL (mysql, mysql2) |
?, ?, … |
sql.values |
The bound values array (both) | — |
sql.debug |
Logging/debugging only — never execute it | inlined (unsafe) |
Composing queries
Nest SQL `` tags directly — the preferred way to build queries from fragments.
Parameterization is preserved across the nesting.
const where = SQL`WHERE active = ${true}`
const sql = SQL`SELECT id, email FROM users ${where} ORDER BY created_at DESC`
For repeated fragments, use the helpers:
SQL.glue(pieces, separator)— join an array of statements (dynamicSET/WHERE, bulkVALUES).SQL.map(array, mapFunc?)— expand an array into bound values (IN (...)lists, bulk insert).
const ids = [1, 2, 3]
const sql = SQL`SELECT * FROM users WHERE id IN (${SQL.map(ids)})`
See references/patterns.md for complete dynamic-query, Fastify,
and migration recipes.
append()still works but is deprecated — prefer nesting tags.
Best practices checklist
- ✅ Wrap every query in the
SQL`` tag; pass interpolated values via${...}. - ✅ Never concatenate strings or use untagged template literals for SQL.
- ⚠️ No
undefined— the tag throws onundefined. Coerce nullable fields tonull:SQL`INSERT INTO users (name, address) VALUES (${user.name}, ${user.address || null})` - ✅ Use
SQL.quoteIdent(name)for dynamic identifiers (table/column names) — never interpolate them as values. - ⚠️ Use
SQL.unsafe(value)only for trusted, non-user values; it bypasses injection protection. - ✅ Use
sql.debugfor logs only — never send it to the driver as the executed query. - ✅ Enforce the tag in your codebase with
eslint-plugin-sqland thethebearingedge.vscode-sql-litVS Code extension for syntax highlighting inside the tag.