Security News
Introducing the Socket Python SDK
The initial version of the Socket Python SDK is now on PyPI, enabling developers to more easily interact with the Socket REST API in Python projects.
feathers-sequelize
Advanced tools
A Feathers database adapter for Sequelize, an ORM for Node.js. It supports PostgreSQL, MySQL, MariaDB, SQLite and MSSQL and features transaction support, relations, read replication and more.
Very Important: Before using this adapter you have to be familiar with both, the Feathers Basics and general use of Sequelize. For associations and relations see the associations section. This adapter may not cover all use cases but they can still be implemented using Sequelize models directly in a Custom Feathers service.
npm install --save feathers-sequelize
And one of the following:
npm install --save pg pg-hstore
npm install --save mysql // For both mysql and mariadb dialects
npm install --save sqlite3
npm install --save tedious // MSSQL
Important:
feathers-sequelize
implements the Feathers Common database adapter API and querying syntax. For more information about models and general Sequelize usage, follow up in the Sequelize documentation.
service(options)
Returns a new service instance initialized with the given options.
const Model = require('./models/mymodel');
const service = require('feathers-sequelize');
app.use('/messages', service({ Model }));
app.use('/messages', service({ Model, id, events, paginate }));
Options:
Model
(required) - The Sequelize model definitionid
(optional, default: 'id'
) - The name of the id field property.raw
(optional, default: true
) - Runs queries faster by returning plain objects instead of Sequelize models.events
(optional) - A list of custom service events sent by this servicepaginate
(optional) - A pagination object containing a default
and max
page sizeWhen making a service method call, params
can contain an sequelize
property which allows to pass additional Sequelize options. This can e.g. be used to retrieve associations. Normally this wil be set in a before hook:
app.service('messages').hooks({
before: {
find(context) {
// Get the Sequelize instance. In the generated application via:
const sequelize = context.app.get('sequelizeClient');
context.params.sequelize = {
include: [ User ]
}
}
}
});
raw
queriesBy default, all feathers-sequelize
operations will return raw
data (using raw: true
when querying the database). This results in faster execution and allows feathers-sequelize to interoperate with feathers-common hooks and other 3rd party integrations. However, this will bypass some of the "goodness" you get when using Sequelize as an ORM:
Don't worry! The solution is easy. Please read the guides about working with model instances.
Here is an example of a Feathers server with a messages
SQLite Sequelize Model:
$ npm install @feathersjs/feathers @feathersjs/errors @feathersjs/express @feathersjs/socketio sequelize feathers-sequelize sqlite3
In app.js
:
const path = require('path');
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const socketio = require('@feathersjs/socketio');
const Sequelize = require('sequelize');
const service = require('feathers-sequelize');
const sequelize = new Sequelize('sequelize', '', '', {
dialect: 'sqlite',
storage: path.join(__dirname, 'db.sqlite'),
logging: false
});
const Message = sequelize.define('message', {
text: {
type: Sequelize.STRING,
allowNull: false
}
}, {
freezeTableName: true
});
// Create an Express compatible Feathers application 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 an in-memory Feathers service with a default page size of 2 items
// and a maximum size of 4
app.use('/messages', service({
Model: Message,
paginate: {
default: 2,
max: 4
}
}));
app.use(express.errorHandler());
Message.sync({ force: true }).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.
The documentation on Sequelize associations and relations is essential to implementing associations with this adapter and one of the steepest parts of the Sequelize learning curve. If you have never used an ORM, let it do a lot of the heavy lifting for you!
params.sequelize.include
Once you understand how the include
option works with Sequelize, you will want to set that option from a before hook in Feathers. Feathers will pass the value of context.params.sequelize
as the options parameter for all Sequelize method calls. This is what your hook might look like:
// GET /my-service?name=John&include=1
function (context) {
if (hook.params.query.include) {
const AssociatedModel = hook.app.services.fooservice.Model;
hook.params.sequelize = {
include: [{ model: AssociatedModel }]
};
// delete any special query params so they are not used
// in the WHERE clause in the db query.
delete context.params.query.include;
}
return Promise.resolve(context);
}
Underneath the hood, feathers will call your models find method sort of like this:
// YourModel is a sequelize model
const options = Object.assign({ where: { name: 'John' }}, context.params.sequelize);
YourModel.findAndCount(options);
For more information, follow up up in the Sequelize documentation for associationsand this issue.
Additionally to the common querying mechanism this adapter also supports all Sequelize query operators.
Note: This adapter supports an additional
$returning
parameter for patch and remove queries. By settingparams.$returning = false
it will disable feathers and sequelize from returning what was changed, so mass updates can be done without overwhelming node and/or clients.
It is highly recommended to use raw
queries, which is the default. However, there are times when you will want to take advantage of Sequelize Instance methods. There are two ways to tell feathers to return Sequelize instances:
Set { raw: false }
in a "before" hook:
function rawFalse(context) {
if (!context.params.sequelize) context.params.sequelize = {};
Object.assign(context.params.sequelize, { raw: false });
return context;
}
hooks.before.find = [rawFalse];
Use the new hydrate
hook in the "after" phase:
const hydrate = require('feathers-sequelize/hooks/hydrate');
hooks.after.find = [hydrate()];
// Or, if you need to include associated models, you can do the following:
function includeAssociated (context) {
return hydrate({
include: [{ model: context.app.services.fooservice.Model }]
}).call(this, context);
}
hooks.after.find = [includeAssociated];
For a more complete example see this gist.
Important: When working with Sequelize Instances, most of the feathers-hooks-common will no longer work. If you need to use a common hook or other 3rd party hooks, you should use the "dehydrate" hook to convert data back to a plain object:
const hydrate = require('feathers-sequelize/hooks/hydrate'); const dehydrate = require('feathers-sequelize/hooks/dehydrate'); const { populate } = require('feathers-hooks-common'); hooks.after.find = [hydrate(), doSomethingCustom(), dehydrate(), populate()];
Sequelize by default gives you the ability to add validations at the model level. Using an error handler like the one that comes with Feathers your validation errors will be formatted nicely right out of the box!
Migrations with feathers and sequelize are quite simple. This guide will walk you through creating the recommended file structure, but you are free to rearrange things as you see fit. The following assumes you have a migrations
folder in the root of your app.
npm install sequelize-cli --save -g
.sequelizerc
file in your project root with the following content:const path = require('path');
module.exports = {
'config': path.resolve('migrations/config/config.js'),
'migrations-path': path.resolve('migrations'),
'seeders-path': path.resolve('migrations/seeders'),
'models-path': path.resolve('migrations/models')
};
migrations/config/config.js
:const app = require('../../src/app');
const env = process.env.NODE_ENV || 'development';
const dialect = 'mysql'|'sqlite'|'postgres'|'mssql';
module.exports = {
[env]: {
dialect,
url: app.get(dialect),
migrationStorageTableName: '_migrations'
}
};
migrations/models/index.js
:const Sequelize = require('sequelize');
const app = require('../../src/app');
const sequelize = app.get('sequelizeClient');
const models = sequelize.models;
// The export object must be a dictionary of model names -> models
// It must also include sequelize (instance) and Sequelize (constructor) properties
module.exports = Object.assign({
Sequelize,
sequelize
}, models);
The migration commands will load your application and it is therefore required that you define the same environment variables as when running you application. For example, many applications will define the database connection string in the startup command:
DATABASE_URL=postgres://user:pass@host:port/dbname npm start
All of the following commands assume that you have defined the same environment variables used by your application.
ProTip: To save typing, you can export environment variables for your current bash/terminal session:
export DATABASE_URL=postgres://user:pass@host:port/db
To create a new migration file, run the following command and provide a meaningful name:
sequelize migration:create --name="meaningful-name"
This will create a new file in the migrations folder. All migration file names will be prefixed with a sortable data/time string: 20160421135254-meaninful-name.js
. This prefix is crucial for making sure your migrations are executed in the proper order.
NOTE: The order of your migrations is determined by the alphabetical order of the migration scripts in the file system. The file names generated by the CLI tools will always ensure that the most recent migration comes last.
Open the newly created migration file and write the code to both apply and undo the migration. Please refer to the sequelize migration functions for available operations. Do not be lazy - write the down script too and test! Here is an example of converting a NOT NULL
column accept null values:
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.changeColumn('tableName', 'columnName', {
type: Sequelize.STRING,
allowNull: true
});
},
down: function (queryInterface, Sequelize) {
return queryInterface.changeColumn('tableName', 'columnName', {
type: Sequelize.STRING,
allowNull: false
});
}
};
ProTip: As of this writing, if you use the
changeColumn
method you must always specify thetype
, even if the type is not changing.
ProTip: Down scripts are typically easy to create and should be nearly identical to the up script except with inverted logic and inverse method calls.
The application code should always be up to date with the migrations. This allows the app to be freshly installed with everything up-to-date without running the migration scripts. Your migrations should also never break a freshly installed app. This often times requires that you perform any necessary checks before executing a task. For example, if you update a model to include a new field, your migration should first check to make sure that new field does not exist:
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.describeTable('tableName').then(attributes => {
if ( !attributes.columnName ) {
return queryInterface.addColumn('tableName', 'columnName', {
type: Sequelize.INTEGER,
defaultValue: 0
});
}
})
},
down: function (queryInterface, Sequelize) {
return queryInterface.describeTable('tableName').then(attributes => {
if ( attributes.columnName ) {
return queryInterface.removeColumn('tableName', 'columnName');
}
});
}
};
The CLI tools will always run your migrations in the correct order and will keep track of which migrations have been applied and which have not. This data is stored in the database under the _migrations
table. To ensure you are up to date, simply run the following:
sequelize db:migrate
ProTip: You can add the migrations script to your application startup command to ensure that all migrations have run every time your app is started. Try updating your package.json
scripts
attribute and runnpm start
:
scripts: {
start: "sequelize db:migrate && node src/"
}
To undo the last migration, run the following command:
sequelize db:migrate:undo
Continue running the command to undo each migration one at a time - the migrations will be undone in the proper order.
Note: - You shouldn't really have to undo a migration unless you are the one developing a new migration and you want to test that it works. Applications rarely have to revert to a previous state, but when they do you will be glad you took the time to write and test your
down
scripts!
In the unfortunate case where you must revert your app to a previous state, it is important to take your time and plan your method of attack. Every application is different and there is no one-size-fits-all strategy for rewinding an application. However, most applications should be able to follow these steps (order is important):
Copyright (c) 2017
Licensed under the MIT license.
v3.0.2 (2018-03-25)
Closed issues:
Merged pull requests:
FAQs
A service adapter for Sequelize an SQL ORM
The npm package feathers-sequelize receives a total of 11,104 weekly downloads. As such, feathers-sequelize popularity was classified as popular.
We found that feathers-sequelize demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The initial version of the Socket Python SDK is now on PyPI, enabling developers to more easily interact with the Socket REST API in Python projects.
Security News
Floating dependency ranges in npm can introduce instability and security risks into your project by allowing unverified or incompatible versions to be installed automatically, leading to unpredictable behavior and potential conflicts.
Security News
A new Rust RFC proposes "Trusted Publishing" for Crates.io, introducing short-lived access tokens via OIDC to improve security and reduce risks associated with long-lived API tokens.