Skip to main content

Middleware

RxDB supports middleware-hooks like mongoose. Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions. The hooks are specified on RxCollection-level and help to create a clear what-happens-when-structure of your code.

Hooks can be defined to run parallel or as series one after another. Hooks can be synchronous or asynchronous when they return a Promise. To stop the operation at a specific hook, throw an error.

List​

RxDB supports the following hooks:

  • preInsert
  • postInsert
  • preSave
  • postSave
  • preRemove
  • postRemove
  • postCreate

Why is there no validate-hook?​

Different to mongoose, the validation on document-data is running on the field-level for every change to a document. This means if you set the value lastName of a RxDocument, then the validation will only run on the changed field, not the whole document. Therefore it is not useful to have validate-hooks when a document is written to the database.

Use Cases​

Middleware are useful for atomizing model logic and avoiding nested blocks of async code. Here are some other ideas:

  • complex validation
  • removing dependent documents
  • asynchronous defaults
  • asynchronous tasks that a certain action triggers
  • triggering custom events
  • notifications

Usage​

All hooks have the plain data as first parameter, and all but preInsert also have the RxDocument-instance as second parameter. If you want to modify the data in the hook, change attributes of the first parameter.

All hook functions are also this-bind to the RxCollection-instance.

Insert​

An insert-hook receives the data-object of the new document.

lifecycle​

  • RxCollection.insert is called
  • preInsert series-hooks
  • preInsert parallel-hooks
  • schema validation runs
  • new document is written to database
  • postInsert series-hooks
  • postInsert parallel-hooks
  • event is emitted to RxDatabase and RxCollection

preInsert​

// series
myCollection.preInsert(function(plainData){
// set age to 50 before saving
plainData.age = 50;
}, false);

// parallel
myCollection.preInsert(function(plainData){

}, true);

// async
myCollection.preInsert(function(plainData){
return new Promise(res => setTimeout(res, 100));
}, false);

// stop the insert-operation
myCollection.preInsert(function(plainData){
throw new Error('stop');
}, false);

postInsert​

// series
myCollection.postInsert(function(plainData, rxDocument){

}, false);

// parallel
myCollection.postInsert(function(plainData, rxDocument){

}, true);

// async
myCollection.postInsert(function(plainData, rxDocument){
return new Promise(res => setTimeout(res, 100));
}, false);

Save​

A save-hook receives the document which is saved.

lifecycle​

  • RxDocument.save is called
  • preSave series-hooks
  • preSave parallel-hooks
  • updated document is written to database
  • postSave series-hooks
  • postSave parallel-hooks
  • event is emitted to RxDatabase and RxCollection

preSave​

// series
myCollection.preSave(function(plainData, rxDocument){
// modify anyField before saving
plainData.anyField = 'anyValue';
}, false);

// parallel
myCollection.preSave(function(plainData, rxDocument){

}, true);

// async
myCollection.preSave(function(plainData, rxDocument){
return new Promise(res => setTimeout(res, 100));
}, false);

// stop the save-operation
myCollection.preSave(function(plainData, rxDocument){
throw new Error('stop');
}, false);

postSave​

// series
myCollection.postSave(function(plainData, rxDocument){

}, false);

// parallel
myCollection.postSave(function(plainData, rxDocument){

}, true);

// async
myCollection.postSave(function(plainData, rxDocument){
return new Promise(res => setTimeout(res, 100));
}, false);

Remove​

An remove-hook receives the document which is removed.

lifecycle​

  • RxDocument.remove is called
  • preRemove series-hooks
  • preRemove parallel-hooks
  • deleted document is written to database
  • postRemove series-hooks
  • postRemove parallel-hooks
  • event is emitted to RxDatabase and RxCollection

preRemove​

// series
myCollection.preRemove(function(plainData, rxDocument){

}, false);

// parallel
myCollection.preRemove(function(plainData, rxDocument){

}, true);

// async
myCollection.preRemove(function(plainData, rxDocument){
return new Promise(res => setTimeout(res, 100));
}, false);

// stop the remove-operation
myCollection.preRemove(function(plainData, rxDocument){
throw new Error('stop');
}, false);

postRemove​

// series
myCollection.postRemove(function(plainData, rxDocument){

}, false);

// parallel
myCollection.postRemove(function(plainData, rxDocument){

}, true);

// async
myCollection.postRemove(function(plainData, rxDocument){
return new Promise(res => setTimeout(res, 100));
}, false);

postCreate​

This hook is called whenever a RxDocument is constructed. You can use postCreate to modify every RxDocument-instance of the collection. This adds a flexible way to add specify behavior to every document. You can also use it to add custom getter/setter to documents. PostCreate-hooks cannot be asynchronous.

myCollection.postCreate(function(plainData, rxDocument){
Object.defineProperty(rxDocument, 'myField', {
get: () => 'foobar',
});
});

const doc = await myCollection.findOne().exec();

console.log(doc.myField);
// 'foobar'
note

This hook does not run on already created or cached documents. Make sure to add postCreate-hooks before interacting with the collection.