Recipes
Using non-standard database that is compatible with PostgreSQL wire protocol (such as CockroachDB)
Specify PostgreSQL version that database you are using is compatible with protocol-wise using version
option, e. g.:
const knex = require('knex')({
client: 'pg',
version: '7.2',
connection: {
host: '127.0.0.1',
user: 'your_database_user',
password: 'your_database_password',
database: 'myapp_test',
},
});
Note that value of version
option should be not the version of the database that you are using, but version of PostgreSQL that most closely matches functionality of the database that you are using. If not provided by database vendor, try using '7.2' as a baseline and keep increasing (within the range of existing PostgreSQL versions) until it starts (or stops) working.
There are also known incompatibilities with migrations for databases that do not support select for update. See https://github.com/tgriesser/knex/issues/2002 for a workaround.
Connecting to MSSQL on Azure SQL Database
{encrypt: true}
should be included in options branch of connection configuration:
knex({
client: 'mssql',
connection: {
database: 'mydatabase',
server: 'myserver.database.windows.net',
user: 'myuser',
password: 'mypass',
port: 1433,
connectionTimeout: 30000,
options: {
encrypt: true,
},
},
});
See all of node-mssql's connection options
Adding a full-text index for PostgreSQL
exports.up = (knex) => {
return knex.schema.createTable('foo', (table) => {
table.increments('id');
table.specificType('fulltext', 'tsvector');
table.index('fulltext', null, 'gin');
});
};
DB access using SQLite and SQLCipher
After you build the SQLCipher source and the npm SQLite3 package, and encrypt your DB (look elsewhere for these things), then anytime you open your database, you need to provide your encryption key using the SQL statement:
PRAGMA KEY = 'secret'
This PRAGMA is more completely documented in the SQLCipher site. When working with Knex this is best done when opening the DB, via the following:
const myDBConfig = {
client: 'sqlite3',
connection: {
filename: 'myEncryptedSQLiteDbFile.db',
},
pool: {
afterCreate: function (conn, done) {
conn.run("PRAGMA KEY = 'secret'");
done();
},
},
};
const knex = require('knex')(myDBConfig);
Of course embedding the key value in your code is a poor security practice. Instead, retrieve the 'secret' from elsewhere.
The key Knex thing to note here is the "afterCreate" function. This is documented in the knexjs.org site, but is not in the Table of Contents at this time, so do a browser find when on the site to get to it. It allows auto-updating DB settings when creating any new pool connections (of which there will only ever be one per file for Knex-SQLite).
If you don't use the "afterCreate" configuration, then you will need to run a knex.raw statement with each and every SQL you execute, something like as follows:
return knex.raw("PRAGMA KEY = 'secret'").then(() =>
knex('some_table')
.select()
.on('query-error', function (ex, obj) {
console.log('KNEX select from some_table ERR ex:', ex, 'obj:', obj);
})
);
Maintaining changelog for seeds (version >= 0.16.0-next1)
In case you would like to use Knex.js changelog functionality to ensure your environments are only seeded once, but don't want to mix seed files with migration files, you can specify multiple directories as a source for your migrations:
await knex.migrate.latest({
directory: [
'src/services/orders/database/migrations',
'src/services/orders/database/seeds',
],
sortDirsSeparately: true,
tableName: 'orders_migrations',
schemaName: 'orders',
});
Using explicit transaction management together with async code
await knex.transaction((trx) => {
async function stuff() {
trx.rollback(new Error('Foo'));
}
stuff().then(() => {
// do something
});
});
Or alternatively:
try {
await knex.transaction((trx) => {
async function stuff() {
trx.rollback(new Error('always explicit rollback this time'));
}
stuff();
});
// transaction was committed
} catch (err) {
// transaction was rolled back
}
(note that promise for knex.transaction
resolves after transaction is rolled back or committed)
Using parentheses with AND operator
In order to generate query along the lines of
SELECT "firstName", "lastName", "status"
FROM "userInfo"
WHERE "status" = 'active'
AND ("firstName" ILIKE '%Ali%' OR "lastName" ILIKE '%Ali%');
you need to use following approach:
queryBuilder
.where('status', status.uuid)
.andWhere((qB) =>
qB
.where('firstName', 'ilike', `%${q}%`)
.orWhere('lastName', 'ilike', `%${q}%`)
);
Calling an oracle stored procedure with bindout variables
How to call and retrieve output from an oracle stored procedure
const oracle = require('oracledb');
const bindVars = {
input_var1: 6,
input_var2: 7,
output_var: {
dir: oracle.BIND_OUT,
},
output_message: {
dir: oracle.BIND_OUT,
},
};
const sp =
'BEGIN MULTIPLY_STORED_PROCEDURE(:input_var1, :input_var2, :output_var, :output_message); END;';
const results = await knex.raw(sp, bindVars);
console.log(results[0]); // 42
console.log(results[1]); // 6 * 7 is the answer to life
Node instance doesn't stop after using knex
Make sure to close knex instance after execution to avoid Node process hanging due to open connections:
async function migrate() {
try {
await knex.migrate.latest({
/**config**/
});
} catch (e) {
process.exit(1);
} finally {
try {
knex.destroy();
} catch (e) {
// ignore
}
}
}
migrate();
Manually Closing Streams
When using Knex's stream interface, you can typically just pipe
the return stream to any writable stream. However, with HTTPIncomingMessage
, you'll need to take special care to handle aborted requests.
An HTTPIncomingMessage
object is typically called request
. This is the first argument in 'request'
events emitted on http.Server
instances. Express's req
implements a compatible interface and Hapi exposes this object on its request objects as request.raw.req
.
You need to explicitly handle the case where an HTTPIncomingMessage
is closed prematurely when streaming from a database with Knex. The easiest way to cause this is:
- Visit an endpoint that takes several seconds to fully transmit a response
- Close the browser window immediately after beginning the request
When this happens while you are streaming a query to a client, you need to manually tell Knex that it can release the database connection in use back to the connection pool.
server.on('request', function (request, response) {
const stream = knex.select('*').from('items').stream();
request.on('close', stream.end.bind(stream));
});