sql-template-tag

repository·main·Indexed 18 days ago

https://github.com/blakeembrey/sql-template-tag

An ES2015 tagged template string library for preparing parameterized SQL statements to prevent SQL injection. It supports multiple placeholder formats compatible with pg, mysql, sqlite, and oracledb via .sql (?), .text ($1), and .statement (:1) properties. Includes helper functions such as join() for IN clauses, bulk() for multi-row inserts, raw() for dynamic fragments, and empty() for conditional logic.

Tokens
2.8K
Snippets
13
Records
13
Agent score
14%

What's inside sql-template-tag

  1. Basic usage of the sql template tag

    main

    Use the sql tagged template literal to create a query object. This object contains different string formats compatible with various database drivers, along with the extracted parameter values.

    • query.sql: Uses ? placeholders (compatible with mysql).
    • query.text: Uses $1, $2, etc. placeholders (compatible with pg).
    • query.statement: Uses :1, :2, etc. placeholders (compatible with oracledb).
    • query.values: An array of the interpolated values.

    You can nest sql instances inside other sql templates.

    import sql, { empty, join, raw } from "sql-template-tag";
    
    const id = 1;
    const query = sql`SELECT * FROM books WHERE id = ${id}`;
    
    // Output formats:
    // query.sql       => "SELECT * FROM books WHERE id = ?"
    // query.text      => "SELECT * FROM books WHERE id = $1"
    // query.statement => "SELECT * FROM books WHERE id = :1"
    // query.values    => [1]
    
    // Database integration examples:
    // pg.query(query);          // Uses `text` and `values`
    // mysql.query(query);       // Uses `sql` and `values`
    // oracledb.execute(query);  // Uses `statement` and `values`
    
    // Nesting SQL instances:
    // const nested = sql`SELECT id FROM authors WHERE name = ${"Blake"}`;
    // const query = sql`SELECT * FROM books WHERE author_id IN (${nested})`;
    
    // Using helpers for conditional queries:
    // sql`SELECT * FROM books ${hasIds ? sql`WHERE ids IN (${join(ids)})` : empty}`;
  2. Integrate with MSSQL

    main

    To use sql-template-tag with the mssql package, pass the strings property and the spread values to the query method.

    // Assuming 'query' is a Sql instance from sql-template-tag
    // and 'mssql' is the mssql driver
    // mssql.query(query.strings, ...query.values);
  3. Use join() to combine values or SQL fragments

    main

    The join() helper accepts an array of values or Sql instances and returns a SQL object where the elements are joined by a separator. This is useful for building IN clauses or dynamic AND/OR conditions.

    import { join, sql } from "sql-template-tag";
    
    // Joining simple values
    const query = join([1, 2, 3]);
    // query.sql       => "?,?,?"
    // query.values    => [1, 2, 3]
    
    // Joining SQL fragments with a custom separator
    const query2 = join(
      [sql`first_name LIKE ${firstName}`, sql`last_name LIKE ${lastName}`],
      " AND "
    );
    // query2.sql => "first_name LIKE ? AND last_name LIKE ?"
  4. Use raw() for dynamic SQL fragments

    main

    The raw() helper accepts a string and returns a Sql instance. This allows you to inject dynamic SQL parts that are not treated as parameters.

    WARNING: Do not pass user-provided input to raw(), as this introduces SQL injection vulnerabilities.

    import { raw } from "sql-template-tag";
    
    raw("SELECT"); // Equivalent to sql`SELECT`
  5. Use bulk() for bulk inserts

    main

    The bulk() helper accepts an array of arrays and returns a SQL object formatted for bulk inserts. It joins the rows using commas and the values into a single flat array.

    import sql, { bulk } from "sql-template-tag";
    
    const query = sql`INSERT INTO users (name) VALUES ${bulk([
      ["Blake"],
      ["Bob"],
      ["Joe"]
    ])}`;
    
    // query.sql    => "INSERT INTO users (name) VALUES (?),(?),(?)"
    // query.values => ["Blake", "Bob", "Joe"]
  6. Use empty() as a placeholder

    main

    The empty() helper provides a placeholder for an empty SQL string. It is equivalent to raw(""). This is particularly useful for conditional logic in template literals where you might want to omit a clause entirely.

    import { empty, sql } from "sql-template-tag";
    
    // If condition is false, no extra SQL is added
    const query = sql`SELECT * FROM books ${condition ? sql`WHERE id = ${id}` : empty}`;
  7. Implement stricter TypeScript types

    main

    By default, the library uses unknown for values to support all possible inputs. If you require stricter type checking for your SQL values, you can define a custom sql function using the Sql class.

    import { Sql } from "sql-template-tag";
    
    type SupportedValue =
      | string
      | number
      | SupportedValue[]
      | { [key: string]: SupportedValue };
    
    function sql(
      strings: ReadonlyArray<string>,
      ...values: Array<SupportedValue | Sql>
    ) {
      return new Sql(strings, values);
    }
  8. Use the sql template tag

    main

    The default export sql is a tagged template function used to create Sql instances. It automatically handles parameterization to prevent SQL injection by separating the query string from the values. You can nest sql instances within each other to build complex queries dynamically.

    import sql from 'sql-template-tag';
    
    const userId = 1;
    const query = sql`SELECT * FROM users WHERE id = ${userId}`;
    
    // query.sql -> "SELECT * FROM users WHERE id = ?"
    // query.values -> [1]
  9. Create bulk insert queries with bulk()

    main

    The bulk() function creates a Sql instance for structured multi-row data, typically used for INSERT statements. It wraps each row in parentheses and joins them.

    Parameters:

    • data: A 2D array (ReadonlyArray<ReadonlyArray<RawValue>>) where each inner array represents a row.
    • separator: The string used to separate rows (defaults to ,).
    • prefix: A string to prepend (defaults to "").
    • suffix: A string to append (defaults to "").

    Note: All rows in the data array must have the same length, otherwise a TypeError is thrown. Calling bulk() with an empty array will also throw a TypeError.

    import { bulk, sql } from 'sql-template-tag';
    
    const data = [
      [1, 'Alice'],
      [2, 'Bob'],
    ];
    
    const query = sql`INSERT INTO users (id, name) ${bulk(data)}`;
    // query.sql -> "INSERT INTO users (id, name) (?,?), (?,?)"
    // query.values -> [1, 'Alice', 2, 'Bob']
  10. Insert raw SQL with raw()

    main

    The raw() function allows you to insert a plain string directly into a query without parameterization. Use this with extreme caution to avoid SQL injection; only use it for trusted, hardcoded SQL fragments.

    import { raw, sql } from 'sql-template-tag';
    
    const tableName = 'users';
    // Use raw for trusted identifiers like table names
    const query = sql`SELECT * FROM ${raw(tableName)} WHERE id = ${1}`;
    // query.sql -> "SELECT * FROM users WHERE id = ?"
  11. Access formatted SQL strings from a Sql instance

    main

    A Sql instance provides three different ways to access the parameterized query string, depending on the placeholder format required by your database driver:

    • .sql: Uses ? placeholders (common for MySQL, SQLite).
    • .statement: Uses :1, :2, etc. (common for Oracle).
    • .text: Uses $1, $2, etc. (common for PostgreSQL).

    All methods also provide access to the .values array containing the actual data.

    const query = sql`SELECT * FROM users WHERE id = ${123}`;
    
    console.log(query.sql);        // "SELECT * FROM users WHERE id = ?"
    console.log(query.statement); // "SELECT * FROM users WHERE id = :1"
    console.log(query.text);      // "SELECT * FROM users WHERE id = $1"
    console.log(query.values);    // [123]