/node-sql-template-strings

ES6 tagged template strings for prepared SQL statements 📋

Primary LanguageJavaScriptISC LicenseISC

SQL Template Strings

npm downloads build codecov dependencies node license chat: on gitter code style: prettier semantic-release

API Documentation

A simple yet powerful module to allow you to use ES6 tagged template strings for prepared/escaped statements.
Works with postgres.

Example for escaping queries (callbacks omitted):

const SQL = require('sql-template-strings')

const book = 'harry potter'
const author = 'J. K. Rowling'

pg.query('SELECT author FROM books WHERE name = $1 AND author = $2', [book, author])
// is equivalent to
pg.query(SQL`SELECT author FROM books WHERE name = ${book} AND author = ${author}`)

This might not seem like a big deal, but when you do an INSERT with a lot columns writing all the placeholders becomes a nightmare:

db.query(
  'INSERT INTO books (name, author, isbn, category, recommended_age, pages, price) VALUES (?, ?, ?, ?, ?, ?, ?)',
  [name, author, isbn, category, recommendedAge, pages, price]
)
// is better written as
db.query(SQL`
  INSERT
  INTO    books
          (name, author, isbn, category, recommended_age, pages, price)
  VALUES  (${name}, ${author}, ${isbn}, ${category}, ${recommendedAge}, ${pages}, ${price})
`)

Also template strings support line breaks, while normal strings do not.

How it works

The SQL template string tag transforms the template string and returns an object that is understood by postgres:

const query = SQL`SELECT author FROM books WHERE name = ${book} AND author = ${author}`
typeof query // => 'object'
query.text   // => 'SELECT author FROM books WHERE name = $1 AND author = $2'
query.values // => ['harry potter', 'J. K. Rowling']

Inserting multiple values with SQL.values()

db.query(SQL`INSERT INTO values (${SQL.values([ [1,2,3], [4,5,6] ])})`

Building complex queries with append()

It is also possible to build queries by appending another query or a string with the append() method (returns this for chaining):

query.append(SQL`AND genre = ${genre}`).append(' ORDER BY rating')
query.text   // => 'SELECT author FROM books WHERE name = $1 AND author = $2 AND genre = $3 ORDER BY rating'
query.values // => ['harry potter', 'J. K. Rowling', 'Fantasy'] ORDER BY rating

This allows you to build complex queries without having to care about the placeholder index or the values array:

const query = SQL`SELECT * FROM books`
if (params.name) {
  query.append(SQL` WHERE name = ${params.name}`)
}
query.append(SQL` LIMIT 10 OFFSET ${params.offset || 0}`)

Raw values

Some values cannot be replaced by placeholders in prepared statements, like table names. append() replaces the SQL.raw() syntax from version 1, just pass a string and it will get appended raw.

Please note that when inserting raw values, you are responsible for quoting and escaping these values with proper escaping functions first if they come from user input (E.g. pg.escapeIdentifier()). Also, executing many prepared statements with changing raw values in a loop will quickly overflow the prepared statement buffer (and destroy their performance benefit), so be careful.

const table = 'books'
const order = 'DESC'
const column = 'author'

db.query(SQL`SELECT * FROM "`.append(table).append(SQL`" WHERE author = ${author} ORDER BY ${column} `).append(order))

// escape user input manually
pg.query(SQL`SELECT * FROM `.append(pg.escapeIdentifier(someUserInput)).append(SQL` WHERE name = ${book} ORDER BY ${column} `).append(order))

Binding Arrays

To bind the array dynamically as a parameter use ANY (PostgreSQL only):

const authors = ['J. K. Rowling', 'J. R. R. Tolkien']
const query = SQL`SELECT name FROM books WHERE author = ANY(${authors})`
query.text   // => 'SELECT name FROM books WHERE author = ANY($1)'
query.values // => ['J. K. Rowling', 'J. R. R. Tolkien']

Prepared Statements in Postgres

Postgres requires prepared statements to be named, otherwise the parameters will be escaped and replaced on the client side. You can set the name with the setName() method:

// old way
pg.query({name: 'my_query', text: 'SELECT author FROM books WHERE name = $1', values: [book]})

// with template strings
pg.query(SQL`SELECT author FROM books WHERE name = ${book}`.setName('my_query'))

You can also set the name property on the statement object directly or use Object.assign().

Editor Integration

Contributing

  • Tests are written using mocha
  • You can use npm test to run the tests and check coding style
  • Since this module is only compatible with ES6 versions of node anyway, use all the ES6 goodies
  • Pull requests are welcome :)