Preventing unintended clause mutation in Knex.js query builders
0 reputation · 13 Mar 2021, 13:05 UTC
0 reputation · 13 Mar 2021, 13:05 UTC
Knex.js query builders are mutable objects. When a partially constructed builder is passed to a helper function or service layer, that external code can append additional clauses like .where(), join(), or select() to the original instance. This risks accidental data exposure or logic bypass if the developer assumes the query is immutable once passed.
Since the library does not currently offer a native 'read-only' mode for the query builder, developers often rely on architectural patterns like repositories to encapsulate the Knex instance to mitigate this risk.
Is there a documented documented method to clone or freeze a query builder to ensure subsequent modifications do not affect the original object? How can one ensure a builder remains unmodified when it is shared across different module layers?
35275 reputation · 13 Mar 2021, 22:19 UTC
Knex.js query builders are mutable, but the library ships a .clone() method that performs a deep copy of the internal state. Freezing the object with Object.freeze() does not stop mutation because Knex mutates non‑enumerable, non‑configurable properties that freeze does not protect.
.clone() returns a new builder instance that is independent of the original..where(), .join(), .select(), etc., called on the clone do not affect the source builder..toSQL() or .toString() yields a read‑only representation that can be re‑instantiated with knex.raw() if needed.const baseQuery = knex('users').select('id').where('active', true);
const safeQuery = baseQuery.clone(); // safe to pass around
safeQuery to helper functions or service layers. Any further clauses added inside those functions will only affect the clone.
const sql = safeQuery.toString(); // or .toSQL().sql
// Store or send sql – it cannot be mutated.
toSQL() outputs. The original should remain unchanged.Object.freeze() on a builder and confirm that mutation methods still execute – this demonstrates why freeze is ineffective.If your application shares the same builder instance across multiple asynchronous flows (e.g., in a global repository), consider whether you also need to guard against later mutations of the original. In that case, cloning at every use‑site is the safest pattern.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.