A database adapter for KnexJS, an SQL query builder for Postgres, MSSQL, MySQL, MariaDB, SQLite3, and Oracle.
npm install --save mysql knex feathers-knex
Important:
feathers-knex
implements the Feathers Common database adapter API and querying syntax.
Note: You also need to install the database driver for the DB you want to use.
Returns a new service instance initialized with the given options.
const knex = require('knex');
const service = require('feathers-knex');
const db = knex({
client: 'sqlite3',
connection: {
filename: './db.sqlite'
}
});
// Create the schema
db.schema.createTable('messages', table => {
table.increments('id');
table.string('text');
});
app.use('/messages', service({
Model: db,
name: 'messages'
}));
app.use('/messages', service({ Model, name, id, events, paginate }));
Options:
Model
(required) - The KnexJS database instancename
(required) - The name of the tableschema
(optional) - The name of the schema table prefix (example:schema.table
)id
(optional, default:'id'
) - The name of the id field property.events
(optional) - A list of custom service events sent by this servicepaginate
(optional) - A pagination object containing adefault
andmax
page sizemulti
(optional) - Allowcreate
with arrays andupdate
andremove
withid
null
to change multiple items. Can betrue
for all methods or an array of allowed methods (e.g.[ 'remove', 'create' ]
)whitelist
(optional) - A list of additional query parameters to allow (e..g[ '$regex', '$geoNear' ]
). Default is the supportedoperators
Returns a KnexJS query with the common filter criteria (without pagination) applied.
When making a service method call, params
can contain an knex
property which allows to modify the options used to run the KnexJS query. See customizing the query for an example.
Here's a complete example of a Feathers server with a messages
SQLite service. We are using the Knex schema builder and SQLite as the database.
$ npm install @feathersjs/feathers @feathersjs/errors @feathersjs/express @feathersjs/socketio feathers-knex knex sqlite3
In app.js
:
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const socketio = require('@feathersjs/socketio');
const service = require('feathers-knex');
const knex = require('knex');
const db = knex({
client: 'sqlite3',
connection: {
filename: './db.sqlite'
}
});
// Create a feathers instance.
const app = express(feathers());
// Turn on JSON parser for REST services
app.use(express.json());
// Turn on URL-encoded parser for REST services
app.use(express.urlencoded({ extended: true }));
// Enable REST services
app.configure(express.rest());
// Enable Socket.io services
app.configure(socketio());
// Create Knex Feathers service with a default page size of 2 items
// and a maximum size of 4
app.use('/messages', service({
Model: db,
name: 'messages',
paginate: {
default: 2,
max: 4
}
}))
app.use(express.errorHandler());
// Clean up our data. This is optional and is here
// because of our integration tests
db.schema.dropTableIfExists('messages').then(() => {
console.log('Dropped messages table');
// Initialize your table
return db.schema.createTable('messages', table => {
console.log('Creating messages table');
table.increments('id');
table.string('text');
});
}).then(() => {
// Create a dummy Message
app.service('messages').create({
text: 'Message created on server'
}).then(message => console.log('Created message', message));
});
// Start the server.
const port = 3030;
app.listen(port, () => {
console.log(`Feathers server listening on port ${port}`);
});
Run the example with node app
and go to localhost:3030/messages.
In addition to the common querying mechanism, this adapter also supports:
Find all records that match all of the given criteria. The following query retrieves all messages that have foo and bar attributes as true.
app.service('messages').find({
query: {
$and: [
{foo: true},
{bar: true}
]
}
});
Through the REST API:
/messages?$and[][foo]=true&$and[][bar]=true
Find all records where the value matches the given string pattern. The following query retrieves all messages that start with Hello
:
app.service('messages').find({
query: {
text: {
$like: 'Hello%'
}
}
});
Through the REST API:
/messages?text[$like]=Hello%
The opposite of $like
; resulting in an SQL condition similar to this: WHERE some_field NOT LIKE 'X'
app.service('messages').find({
query: {
text: {
$notlike: '%bar'
}
}
});
Through the REST API:
/messages?text[$notlike]=%bar
For PostgreSQL only, the keywork $ilike can be used instead of $like to make the match case insensitive. The following query retrieves all messages that start with hello
(case insensitive):
app.service('messages').find({
query: {
text: {
$ilike: 'hello%'
}
}
});
Through the REST API:
/messages?text[$ilike]=hello%
The Knex adapter comes with three hooks that allows to run service method calls in a transaction. They can be used as application wide (app.hooks.js
) hooks or per service like this:
// A common hooks file
const { hooks } = require('feathers-knex');
const { transaction } = hooks;
module.exports = {
before: {
all: [ transaction.start() ],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
after: {
all: [ transaction.end() ],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
},
error: {
all: [ transaction.rollback() ],
find: [],
get: [],
create: [],
update: [],
patch: [],
remove: []
}
};
To use the transactions feature, you must ensure that the three hooks (start, end and rollback) are being used.
At the start of any request, a new transaction will be started. All the changes made during the request to the services that are using the feathers-knex
will use the transaction. At the end of the request, if sucessful, the changes will be commited. If an error occurs, the changes will be forfeit, all the creates
, patches
, updates
and deletes
are not going to be commited.
The object that contains transaction
is stored in the params.transaction
of each request.
Important: If you call another Knex service within a hook and want to share the transaction you will have to pass
context.params.transaction
in the parameters of the service call.
In a find
call, params.knex
can be passed a KnexJS query (without pagination) to customize the find results.
Combined with .createQuery({ query: {...} })
, which returns a new KnexJS query with the common filter criteria applied, this can be used to create more complex queries. The best way to customize the query is in a before hook for find
.
app.service('messages').hooks({
before: {
find(context) {
const query = context.service.createQuery(context.params);
// do something with query here
query.orderBy('name', 'desc');
context.params.knex = query;
return context;
}
}
});
For using knex's migration CLI, we need to make the configuration available by the CLI. We can do that by providing a knexfile.js
in the root folder with the following contents:
const app = require('./src/app')
module.exports = app.get('postgres')
You will need to replace the postgres
part with the adapter you are using. You will also need to add a migrations
key to your feathersjs config under your database adapter. Optionally, add a seeds
key if you will be using seeds.
// src/config/default.json
...
"postgres": {
"client": "pg",
"connection": "postgres://user:password@localhost:5432/database",
"migrations": {
"tableName": "knex_migrations"
},
"seeds": {
"directory": "../src/seeds"
}
}
Then, by running: knex migrate:make create-users
, a migrations
directory will be created, with the new migration.
As of version 4.0.0 feathers-knex
only throws Feathers Errors with the message. On the server, the original error can be retrieved through a secure symbol via error[require('feathers-knex').ERROR]
const { ERROR } = require('feathers-knex');
try {
await knexService.doSomething();
} catch(error) {
// error is a FeathersError with just the message
// Safely retrieve the Knex error
const knexError = error[ERROR];
}
Copyright (c) 2019
Licensed under the MIT license.