Common API

All database adapters implement a common interface for initialization, pagination, extending and querying. This chapter describes the common adapter initialization and options, how to enable and use pagination, the details on how specific service methods behave and how to extend an adapter with custom functionality.

Important: Every database adapter is an implementation of the Feathers service interface. We recommend being familiar with services, service events and hooks before using a database adapter.

service([options])

Returns a new service instance initialized with the given options.

  1. const service = require('feathers-<adaptername>');
  2. app.use('/messages', service());
  3. app.use('/messages', service({ id, events, paginate }));

Options:

  • id (optional) - The name of the id field property (usually set by default to id or _id).
  • events (optional) - A list of custom service events sent by this service
  • paginate (optional) - A pagination object containing a default and max page size

Pagination

When initializing an adapter you can set the following pagination options in the paginate object:

  • default - Sets the default number of items when $limit is not set
  • max - Sets the maximum allowed number of items per page (even if the $limit query parameter is set higher)

When paginate.default is set, find will return an page object (instead of the normal array) in the following form:

  1. {
  2. "total": "<total number of records>",
  3. "limit": "<max number of items per page>",
  4. "skip": "<number of skipped items (offset)>",
  5. "data": [/* data */]
  6. }

The pagination options can be set as follows:

  1. const service = require('feathers-<db-name>');
  2. // Set the `paginate` option during initialization
  3. app.use('/todos', service({
  4. paginate: {
  5. default: 5,
  6. max: 25
  7. }
  8. }));
  9. // override pagination in `params.paginate` for this call
  10. app.service('todos').find({
  11. paginate: {
  12. default: 100,
  13. max: 200
  14. }
  15. });
  16. // disable pagination for this call
  17. app.service('todos').find({
  18. paginate: false
  19. });

Note: Disabling or changing the default pagination is not available in the client. Only params.query is passed to the server (also see a workaround here)

Pro tip: To just get the number of available records set $limit to 0. This will only run a (fast) counting query against the database.

Service methods

This section describes specifics on how the service methods are implemented for all adapters.

adapter.Model

If the ORM or database supports models, the model instance or reference to the collection belonging to this adapter can be found in adapter.Model. This allows to easily make custom queries using that model, e.g. in a hook:

  1. // Make a MongoDB aggregation (`messages` is using `feathers-mongodb`)
  2. app.service('messages').hooks({
  3. before: {
  4. async find(context) {
  5. const results = await service.Model.aggregate([
  6. { $match: {item_id: id} }, {
  7. $group: {_id: null, total_quantity: {$sum: '$quantity'} }
  8. }
  9. ]).toArray();
  10. // Do something with results
  11. return context;
  12. }
  13. }
  14. });

adapter.find(params)

adapter.find(params) -> Promise returns a list of all records matching the query in params.query using the common querying mechanism. Will either return an array with the results or a page object if pagination is enabled.

Important: When used via REST URLs all query values are strings. Depending on the database the values in params.query might have to be converted to the right type in a before hook.

  1. // Find all messages for user with id 1
  2. app.service('messages').find({
  3. query: {
  4. userId: 1
  5. }
  6. }).then(messages => console.log(messages));
  7. // Find all messages belonging to room 1 or 3
  8. app.service('messages').find({
  9. query: {
  10. roomId: {
  11. $in: [ 1, 3 ]
  12. }
  13. }
  14. }).then(messages => console.log(messages));

Find all messages for user with id 1

  1. GET /messages?userId=1

Find all messages belonging to room 1 or 3

  1. GET /messages?roomId[$in]=1&roomId[$in]=3

adapter.get(id, params)

adapter.get(id, params) -> Promise retrieves a single record by its unique identifier (the field set in the id option during initialization).

  1. app.service('messages').get(1)
  2. .then(message => console.log(message));
  1. GET /messages/1

adapter.create(data, params)

adapter.create(data, params) -> Promise creates a new record with data. data can also be an array to create multiple records.

  1. app.service('messages').create({
  2. text: 'A test message'
  3. })
  4. .then(message => console.log(message));
  5. app.service('messages').create([{
  6. text: 'Hi'
  7. }, {
  8. text: 'How are you'
  9. }])
  10. .then(messages => console.log(messages));
  1. POST /messages
  2. {
  3. "text": "A test message"
  4. }

adapter.update(id, data, params)

adapter.update(id, data, params) -> Promise completely replaces a single record identified by id with data. Does not allow replacing multiple records (id can’t be null). id can not be changed.

  1. app.service('messages').update(1, {
  2. text: 'Updates message'
  3. })
  4. .then(message => console.log(message));
  1. PUT /messages/1
  2. { "text": "Updated message" }

adapter.patch(id, data, params)

adapter.patch(id, data, params) -> Promise merges a record identified by id with data. id can be null to allow replacing multiple records (all records that match params.query the same as in .find). id can not be changed.

  1. app.service('messages').patch(1, {
  2. text: 'A patched message'
  3. }).then(message => console.log(message));
  4. const params = {
  5. query: { read: false }
  6. };
  7. // Mark all unread messages as read
  8. app.service('messages').patch(null, {
  9. read: true
  10. }, params);
  1. PATCH /messages/1
  2. { "text": "A patched message" }

Mark all unread messages as read

  1. PATCH /messages?read=false
  2. { "read": true }

adapter.remove(id, params)

adapter.remove(id, params) -> Promise removes a record identified by id. id can be null to allow removing multiple records (all records that match params.query the same as in .find).

  1. app.service('messages').remove(1)
  2. .then(message => console.log(message));
  3. const params = {
  4. query: { read: true }
  5. };
  6. // Remove all read messages
  7. app.service('messages').remove(null, params);
  1. DELETE /messages/1

Remove all read messages

  1. DELETE /messages?read=true

Extending Adapters

There are two ways to extend existing database adapters. Either by extending the ES6 base class or by adding functionality through hooks.

ProTip: Keep in mind that calling the original service methods will return a Promise that resolves with the value.

Hooks

The most flexible option is weaving in functionality through hooks. For example, createdAt and updatedAt timestamps could be added like this:

  1. const feathers = require('@feathersjs/feathers');
  2. // Import the database adapter of choice
  3. const service = require('feathers-<adapter>');
  4. const app = feathers().use('/todos', service({
  5. paginate: {
  6. default: 2,
  7. max: 4
  8. }
  9. }));
  10. app.service('todos').hooks({
  11. before: {
  12. create: [
  13. (context) => context.data.createdAt = new Date()
  14. ],
  15. update: [
  16. (context) => context.data.updatedAt = new Date()
  17. ]
  18. }
  19. });
  20. app.listen(3030);

Classes (ES6)

All modules also export an ES6 class as Service that can be directly extended like this:

  1. 'use strict';
  2. const { Service } = require( 'feathers-<database>');
  3. class MyService extends Service {
  4. create(data, params) {
  5. data.created_at = new Date();
  6. return super.create(data, params);
  7. }
  8. update(id, data, params) {
  9. data.updated_at = new Date();
  10. return super.update(id, data, params);
  11. }
  12. }
  13. app.use('/todos', new MyService({
  14. paginate: {
  15. default: 2,
  16. max: 4
  17. }
  18. }));