feathers-sequelize

repository·master·Indexed 42 days ago

https://github.com/feathersjs-ecosystem/feathers-sequelize

A service adapter for FeathersJS that uses the Sequelize SQL ORM to provide database services. It allows developers to map Sequelize models to Feathers services, supporting complex querying, associations, pagination, and string-based query operators. Version 8.0.0-pre.3 supports PostgreSQL, MySQL, MariaDB, SQLite, and MSSQL.

Tokens
7.7K
Snippets
21
Records
35
Agent score
51%

What's inside feathers-sequelize

  1. Requirement: Primary keys for all tables

    master
    Every table used by a feathers-sequelize service must have a primary key. Even if a table is used for many-to-many associations and traditionally wouldn't have one, feathers-sequelize requires it because service methods rely on an id and Feathers maps services to specific URL paths. Without a primary key, the service will break.
  2. Handle Sequelize raw query caveats

    master

    By default, feathers-sequelize runs queries with { raw: true }. This improves performance and compatibility with Feathers hooks but bypasses Sequelize-specific features:

    • Custom getters/setters
    • Model-level validations
    • Certain association loading behaviors

    Solutions:

    1. Per-call override: Pass { raw: false } in params.sequelize for specific service calls where you need full model instances.
    2. Global behavior: Refer to the guide on working with model instances (available in later segments) to handle data correctly when using raw results.
  3. How to use Sequelize associations with Feathers

    master

    To implement associations (e.g., include), use a Feathers before hook to populate context.params.sequelize. Feathers passes the value of context.params.sequelize as the options parameter to all underlying Sequelize method calls.

    Note: You must understand Sequelize's include syntax to use this effectively. The hook extracts an include parameter from the query and moves it into the sequelize options object.

    // GET /my-service?name=John&include=1
    function (context) {
      const { include, ...query } = context.params.query;
    
       if (include) {
          const AssociatedModel = context.app.services.fooservice.Model;
          context.params.sequelize = {
            include: [{ model: AssociatedModel }]
          };
          // Update the query to not include `include`
          context.params.query = query;
       }
    
       return context;
    }
  4. Install feathers-sequelize and dependencies

    master

    To set up a Feathers server with SQLite and feathers-sequelize, install the following packages:

    npm install @feathersjs/feathers @feathersjs/errors @feathersjs/express @feathersjs/socketio sequelize feathers-sequelize sqlite3
  5. Testing and integrating complex Sequelize queries

    master

    If you are writing advanced Sequelize queries, follow this two-step workflow:

    1. Test in isolation

    Create a temporary script to test the query directly against the Sequelize client without the Feathers overhead. This allows you to iterate quickly on the query object.

    // test.js
    import app from './src/app';
    app.setup(); // Initialize relations
    
    const seqClient = app.get('sequelizeClient');
    const SomeModel = seqClient.models['some-model'];
    
    SomeModel.findAll({
      /* Your custom query object */
    }).then(console.log).catch(console.log);

    2. Integrate via a 'before' hook

    Once the query works, move the query object into a Feathers before hook by assigning it to context.params.sequelize.

    Important: context.params.sequelize is shallow merged onto the underlying query object generated by Feathers. It is not a deep merge. You can override where, limit, offset, and order by setting them here.

    function buildCustomQuery(context) {
      context.params.sequelize = {
        /* The object tested in your isolation script */
      };
    }
    
    someService.hooks({
      before: {
        find: [buildCustomQuery]
      }
    });
  6. Install feathers-sequelize and database drivers

    master

    To use feathers-sequelize, install the package along with the driver for your specific database dialect.

    Core package:

    npm install --save feathers-sequelize@pre

    Database drivers (choose one):

    • PostgreSQL: npm install --save pg pg-hstore
    • MySQL or MariaDB: npm install --save mysql2
    • SQLite: npm install --save sqlite3
    • MSSQL: npm install --save tedious
    npm install --save feathers-sequelize@pre
    npm install --save pg pg-hstore
  7. Migrate to Feathers v5 (dove)

    master

    If you are upgrading from Feathers v4 to v5, note the following breaking changes in feathers-sequelize:

    1. Named Exports

    The default export has been removed. You must now import SequelizeService directly.

    import { SequelizeService } from 'feathers-sequelize';
    
    app.use('/messages', new SequelizeService({ ... }));

    2. Rename operators to operatorMap

    The options.operators property is now options.operatorMap.

    import { SequelizeService } from 'feathers-sequelize';
    import { Op } from 'sequelize';
    
    app.use('/messages', new SequelizeService({
      Model,
      operatorMap: {
        $between: Op.between
      }
    }));

    3. Use filters for validation

    Feathers v5 uses a filters option to verify filters. Add any $dollar.notation$ operators here.

    import { SequelizeService } from 'feathers-sequelize';
    
    app.use('/messages', new SequelizeService({
      Model,
      filters: {
        '$and': true,
        '$person.name$': true
      }
    }));
    import { SequelizeService } from 'feathers-sequelize';
    
    app.use('/messages', new SequelizeService({
      Model,
      operatorMap: {
        $between: Op.between
      },
      filters: {
        '$and': true,
        '$person.name$': true
      }
    }));
  8. Pass Sequelize options via params.sequelize

    master

    You can pass additional Sequelize-specific options (like include for associations) by attaching a sequelize object to params. This is typically done within a Feathers before hook.

    Example: Retrieving associations

    app.service('messages').hooks({
      before: {
        find(context) {
          // Access the sequelize client from the app
          const sequelize = context.app.get('sequelizeClient');
          const { User } = sequelize.models;
    
          // Pass the include option to Sequelize
          context.params.sequelize = {
            include: [ User ]
          }
    
          return context;
        }
      }
    });

    Warning: If you set a top-level where property in params.sequelize (common for eager loading), it will overwrite the standard Feathers query object.

  9. Set up Sequelize migrations with feathers-sequelize

    master

    To manage database schema changes, use the sequelize-cli. This setup assumes a migrations folder exists in your project root.

    1. Install the CLI

    npm install sequelize-cli --save -g

    2. Configure .sequelizerc

    Create a .sequelizerc file in your project root to map the directory structure:

    const path = require('path');
    
    module.exports = {
      'config': path.resolve('migrations/config.js'),
      'migrations-path': path.resolve('migrations/scripts'),
      'seeders-path': path.resolve('migrations/seeders'),
      'models-path': path.resolve('migrations/models.js')
    };

    3. Create Migration Configuration

    In migrations/config.js, link your Feathers app to the Sequelize configuration:

    const app = require('../src/app');
    const env = process.env.NODE_ENV || 'development';
    const dialect = 'postgres'; // Or your dialect name
    
    module.exports = {
      [env]: {
        dialect,
        url: app.get(dialect),
        migrationStorageTableName: '_migrations'
      }
    };

    4. Define Models Configuration

    In migrations/models.js, export your existing Sequelize models from the Feathers app:

    const Sequelize = require('sequelize');
    const app = require('../src/app');
    const sequelize = app.get('sequelizeClient');
    const models = sequelize.models;
    
    module.exports = Object.assign({
      Sequelize,
      sequelize
    }, models);
  10. Configure default sort order for MSSQL

    master

    When using MSSQL, you must apply a default sort order to avoid the error: Invalid usage of the option NEXT in the FETCH statement.

    Option 1: In the Model definition

    model.beforeFind(model => model.order.push(['id', 'ASC']))

    Option 2: In a Feathers hook

    export default function (options = {}) {
      return async context => {
        const { query = {} } = context.params;
        // Ensure a sort is applied (e.g., by id ascending)
        const $sort = { id: 1 };
    
        context.params.query = {
          $sort: $sort,
          ...query
        };
    
        return context;
      }
    }
  11. Manage Sequelize migrations workflow

    master

    Migrations require your application's environment variables (like DATABASE_URL) to be present in the terminal session.

    Create a migration

    Generate a new migration file with a descriptive name:

    sequelize migration:create --name="meaningful-name"

    Files are stored in migrations/scripts with a timestamp prefix (e.g., 20160421135254-meaningful-name.js) to ensure correct execution order.

    Writing up/down scripts

    Each migration must implement up (to apply) and down (to undo) functions.

    Note: When using changeColumn, you must always specify the type, even if the type is not changing.

    Example of changing a column from NOT NULL to nullable:

    '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
        });
      }
    };

    Apply and Undo migrations

    • Apply all pending migrations: sequelize db:migrate
    • Undo the last migration: sequelize db:migrate:undo

    ProTip: You can automate migrations by adding them to your app startup script in package.json:

    "scripts": {
        "start": "sequelize db:migrate && node src/"
    }
    sequelize migration:create --name="meaningful-name"
    sequelize db:migrate
    sequelize db:migrate:undo
  12. How to return Sequelize Model instances instead of raw objects

    master

    By default, feathers-sequelize returns raw data. To return full Sequelize Model instances (which allow you to use instance methods), you have two options:

    1. Before Hook: Set { raw: false } in context.params.sequelize.
    2. After Hook: Use the hydrate hook provided by feathers-sequelize.

    Warning: When using Sequelize Instances, many feathers-hooks-common hooks will fail. You should use the dehydrate hook to convert data back to plain objects before passing it to standard Feathers hooks.

    // Option 1: Before hook
    const rawFalse = () => (context) => {
      if (!context.params.sequelize) context.params.sequelize = {};
      Object.assign(context.params.sequelize, { raw: false });
      return context;
    }
    
    // Option 2: After hook with hydrate
    import { hydrate } from 'feathers-sequelize';
    
    export default {
      after: {
        find: [hydrate()]
      }
    };
    
    // Recommended pattern for compatibility with other hooks:
    import { dehydrate, hydrate } from 'feathers-sequelize';
    import { populate } from 'feathers-hooks-common';
    
    export default {
      after: {
        find: [hydrate(), doSomethingCustom(), dehydrate(), populate()]
      }
    };