Dynamically generating SQL queries using Node.js
Tags: node, javascript, postgres, sql
Published: 2019-08-29 18:13:48

---
title: Dynamically generating SQL queries using Node.js
published: true
description: Introduction to building dynamic SQL queries safely in Node.js using Slonik. 
tags: node.js, javascript, postgresql, sql 
---

Ever since I have released [Slonik](https://github.com/gajus/slonik) (PostgreSQL client for Node.js) and written a controversial [Stop using Knex.js](https://medium.com/@gajus/stop-using-knex-js-and-earn-30-bf410349856c) article (tl;dr; query builders are designed to be building blocks for ORMs; they do not add value when majority of the query is static.), I have been asked a lot – then how do I generate dynamic queries? I will answer this by sharing a couple of real-life examples.

All of the queries in this article are actual queries used in real-life business, [Applaudience](http://applaudience.com/) which [heavily relies on PostgreSQL](https://medium.com/@gajus/lessons-learned-scaling-postgresql-database-to-1-2bn-records-month-edc5449b3067).

Disclaimer: (1) All examples discuss only SQL injection threats. Authorization logic (e.g. whitelisting columns user is authorized to access) is not in the scope of this article. (2) All statements assume there are no bugs in Slonik implementation.

## Static query with dynamic value bindings

If your query logic does not change depending on user's input, then simply construct SQL query using [`sql` tagged template literal](https://github.com/gajus/slonik#slonik-value-placeholders-tagged-template-literals), e.g.

```js
sql`
  SELECT c1.country_id
  FROM cinema_movie_name cmn1
  INNER JOIN cinema c1 ON c1.id = cmn1.cinema_id
  WHERE cmn1.id = ${cinemaMovieNameId}
`;

```

If you are using Slonik, it is [safe to pass values as template literal placeholders](https://github.com/gajus/slonik#protecting-against-unsafe-value-interpolation). `sql` will interpret all placeholder tokens and construct final SQL query. In this case, the only dynamic part of the query is the value bindings themselves, therefore the final query is:

```sql
SELECT c1.country_id
FROM cinema_movie_name cmn1
INNER JOIN cinema c1 ON c1.id = cmn1.cinema_id
WHERE cmn1.id = $1

```

Query and bound values will be sent to PostgreSQL separately: no risk of SQL injection.

### Binding a list of values

When your query input is a list of values (e.g. such as when retrieving rows matching multiple identifiers), then you _may_ use `sql.valueList`, e.g.

```js
sql`
  SELECT m1.*
  FROM movie m1
  WHERE m1.id IN (${sql.valueList(movieIds)})
`;

```

This will generate a query with dynamic set of value bindings, i.e. if `movieIds` is `[1, 2, 3]` the query that is sent to PostgreSQL will be:

```sql
SELECT m1.*
FROM movie m1
WHERE m1.id IN ($1, $2, $3)

```

However, despite this being a common pattern, I do not advise to use this pattern. Instead, use `sql.array`, e.g.

```js
sql`
  SELECT m1.*
  FROM movie m1
  WHERE m1.id = ANY(${sql.array(movieIds, 'int4')})
`;

```

This will generate a fixed-length query that does not change based on its inputs, i.e.

```sql
SELECT m1.*
FROM movie m1
WHERE m1.id = ANY($1::"int4"[])

```

Continue reading [`sql.array` vs `sql.valueList`](https://github.com/gajus/slonik#sqlarray-vs-sqlvaluelist).

## Query with dynamic columns

If your query result refers to columns that depend on user's input, then use [`sql.identifier`](https://github.com/gajus/slonik#slonik-query-building-sql-identifier) to generate SQL that identifies those columns, e.g.

(Note: Not an actual query used in business. See next paragraph.)

```js
sql`
  SELECT m1.id, ${sql.identifier(['m1', movieTableColumnName])}
  FROM movie m1
  WHERE
    m1.id = ${moveId}
`;

```

This query will produce a query that selects exactly 1 dynamically identified column. There is no risk of SQL injection, i.e. even if logic leading to generation of `movieTableColumnName` was somehow compromised, the worst that can happen is that query attacker will be able to return any column under `m1` alias or execute query with invalid column identifier values (both carry risk; business logic is not in scope of this article).

Just because you can do this, you probably shouldn't. When your application requires to return different columns depending on user's query, it is better to select all columns that are in scope of the business logic and pick value of the needed column, i.e. If the intent of the latter query was to return a different movie identifier based on `movieTableColumnName`, then it is better to write a static query:


```js
sql`
  SELECT
    m1.id,
    m1.foreign_comscore_id,
    m1.foreign_imdb_id,
    m1.foreign_metacritic_id
    m1.foreign_rottentomatoes_id,
    m1.foreign_tmdb_id,
    m1.foreign_webedia_id
  FROM movie m1
  WHERE
    m1.id = ${moveId}
`;

```

The latter has does return some superfluous data on every query, but it has several advantages:

1. It reduces risk of SQL injection (regardless of how much you trust code generation logic, static code is always safer than dynamic code).
1. It produces only one entry `pg_stat_statements`. You will learn to appreciate as few as possible queries in `pg_stat_statements` as your application scales.

## Query with multiple dynamic columns

Same as the above, but [`sql.identifierList`](https://github.com/gajus/slonik#slonik-query-building-sql-identifierlist).

## Nesting dynamic SQL queries 

`sql` tagged template literals can be nested, e.g.

(Note: Simplified version of an actual query used in business.)

```js
const futureEventEventChangeSqlToken = sql`
  SELECT
    ec1.event_id,
    ec1.seat_count,
    ec1.seat_sold_count
  FROM event_change_future_event_view ec1
`;

sql`
  SELECT
    event_id,
    seat_count,
    seat_sold_count
  FROM (
    ${futureEventEventChangeSqlToken}
  ) AS haystack
  WHERE ${paginatedWhereSqlToken}
  ORDER BY ${orderSqlToken}
  LIMIT ${limitSqlToken}
`

```

This allows to pass pre-bound SQL queries as first-class citizens across your program. This is handy when the intent is to isolate SQL generation logic for testing or when large SQL fragments are shared between queries or when the intent is to simply reduce concentration of code complexity in one place.

## Injecting dynamic SQL fragments

`sql.raw` is used to inject dynamic SQL fragments, i.e.

```js
sql`
  SELECT ${sql.raw('foo bar baz')}
`

```

translates to (invalid) query:

```sql
SELECT foo bar baz

```

Unlike the previous example using `sql` tagged template, `sql.raw` is not safe – it allows to create dynamic SQL using user input.

There are no known use cases for generating queries using `sql.raw` that aren't covered by nesting bound `sql` expressions (described in "Nesting dynamic SQL queries") or by one of the other existing [query building methods](https://github.com/gajus/slonik#slonik-query-building). `sql.raw` exists as a mechanism to execute externally stored _static_ (e.g. queries stored in files).

## Query with a dynamic comparison predicate members or operator

If an operator of a comparison predicate present in your query is dynamic, then use [`sql.comparisonPredicate`](https://github.com/gajus/slonik#slonik-query-building-sql-comparisonpredicate), e.g.

(Note: Not an actual query used in business.)

```js
sql`
  SELECT
    c1.id,
    c1.nid,
    c1.name
  FROM cinema c1
  WHERE
    ${sql.comparisonPredicate(
      sql`c1.name`,
      nameComparisonOperator,
      nameComparisonValue
    )}
`;


```

`nameComparisonOperator` can be values such as `=`, `>`, `<`, etc. Assuming `nameComparisonOperator` is "=", then the resulting query is going to be:

```sql
SELECT
  c1.id,
  c1.nid,
  c1.name
FROM cinema c1
WHERE
  c1.name = $1

```

The latter is an extremely rare use case, reserved almost entirely to building higher level SQL abstraction tools (such as ORMs). It may be useful for "advance search" scenarios, however continue reading to familiarise with alternative patterns (see `sql.booleanExpression`).

## Query with dynamic WHERE clause members

If presence of `WHERE` clause members is dynamic, then use [`sql.booleanExpression`](https://github.com/gajus/slonik#sqlbooleanexpression).

```js
const findCinemas = (root, parameters, context) => {
  const booleanExpressions = [
    sql`TRUE`,
  ];

  if (parameters.input.query) {
    const query = parameters.input.query;

    if (query.countryId !== undefined) {
      booleanExpressions.push(
        sql`c2.id = ${query.countryId}`
      );
    }

    if (query.nid !== undefined) {
      booleanExpressions.push(
        sql`c1.nid % ${query.nid}`
      );
    }

    if (query.name !== undefined) {
      booleanExpressions.push(
        sql`c1.name % ${query.name}`
      );
    }
  }

  const whereSqlToken = sql.booleanExpression(
    booleanExpressions,
    'AND'
  );

  return context.pool.any(sql`
    SELECT
      c1.id,
      c1.nid,
      c1.name,
      c2.code_alpha_2 country_code,
      c2.name country_name
    FROM cinema c1
    INNER JOIN country c2 ON c2.id = c1.country_id
    WHERE ${whereSqlToken}
  `);
},

```

`findCinemas` is an implementation of a GraphQL resolver. WHERE clause of the query is constructed using a combination of 3 possible boolean expressions. As is the case with all the other query building methods in Slonik, all expressions can be nested: you can have other boolean expressions as members of a boolean expression or even SQL expression constructed using `sql` tagged template literal.

## Summary

These examples cover every common dynamic SQL building scenario and provide enough knowledge of how Slonik works to enable reader to continue journey of familiarising with [other query building methods provided by Slonik](https://github.com/gajus/slonik#slonik-query-building). The primary intent of this article was to demonstrate that Slonik provides a safe abstraction for constructing SQL queries keeping the static parts of the query intact.

If you value my work and want to see Slonik and [many other of my](https://github.com/gajus/) Open-Source projects to be continuously improved, then please consider becoming a patron:

[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/gajus)
[![Become a Patron](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/gajus)

Finally, I missed a use case scenario that you would like me to cover, mention it in the comments and I will happily include it.