Modli

Data Modeling, Versioning, and CRUD made easy



Wouldn't it be great if every data source you interfaced with used the same model structure, versioning and methods? We thought so too.

Modli is an extensible node module with a set of easily-installed adapters to allow you to work with one or many data sources easily and with a similar syntax.



Available Modules & Adapters

Modli on Github

Use

Install Modli

To get started, you need to have the Modli core installed in your project:

npm install modli --save

Setup The Datasource

The example below shows a simple example of setting up and using Modli with the NeDB adapter.

First, install the adapter:

npm install modli-nedb --save

Then add the adapter, create a model, and use them together as an object to interact with the datasource.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Import modli and the NeDB adapter
import { model, adapter, use, Joi } from 'modli';
import nedb from 'modli-nedb';

// Add an adapter instance
adapter.add({
  name: 'modliNeDB'
  source: nedb,
  config: {
    inMemoryOnly: true
  }
});

// Add a data model
model.add({
  name: 'modliTest',
  version: 1,
  schema: {
    id: Joi.number().integer(),
    fname: Joi.string().min(3).max(30),
    lname: Joi.string().min(3).max(30),
    email: Joi.string().email().min(3).max(254).required()
  }
});

// Create an instance of the datasource object with the Model and Adapter
const myDataSource = use('modliTest', 'modliNeDB');

Now the myDataSource object can be acted upon using the CRUD methods available:

Interact with the Datasource

For example, you could add to the datastore with the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
myDataSource.create({
    id: 12345,
    fname: 'John',
    lname: 'Smith',
    email: 'jsmith@email.com'
  })
  .then((result) => {
    console.log('Success:', result);
  })
  .catch((err) => {
    console.log('Error:', err);
  });

You could then read the created record with the following:

1
2
3
4
5
6
7
myDataSource.read({ id: 12345 })
  .then((results) => {
    console.log('Success:', results);
  })
  .catch((err) => {
    console.log('Error:', err);
  });

All adapters include the create, read, update and delete methods which means you have full CRUD operations out of the gate.

Version

Model Versioning

When a model is added it gets a version property. This version is then used for both validation and sanitization meaning both input and output data are aligned with the model in use.

Models can contain any number of versions using any format preferred:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Version 1
model.add({
  name: 'modliTest',
  version: 1
  schema: { /*...*/ }
});

// Version 2
model.add({
  name: 'modliTest',
  version: 2
  schema: { /*...*/ }
});

// ...

Modli will always default to the last version added so if only one version exists, or a method does not receive a version it will use choose the latest.

Specifying the model to use may vary slightly by adapter, but typically it’s an (optional) param on the method, for example:

1
2
// Create record using version 1 of the model:
myDataSource.create({ /*... data ... */ }, 1)

Modli adapters also sanitize response data objects (and arrays) so if the response is different than the model version specified it will return the correct data format.

extend

Extending

The idea behind Modli was to remove the repetitive components from data modeling and CRUD operation, but in development there is no such thing as “one size fits all”.

As such, Modli (and its adapters) are built to be easily extensible.

Plugins

Modli supports easy addition of custom functionality through plugins. Using the myDataSource instance created in the above section a plugin can be added to extend the Modli instance:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Create a plugin function
const getSchemas = function () {
  return this.schemas;
}

// Use the `plugin` method to add the plugin
myDataSource.plugin(getSchemas);

// Call the plugin
const schemas = myDataSource.getSchemas(); // <- would return all schemas in user

Extending the Model

Models require 3 properties: name, version and schema. Beyond that there are no restrictions.

1
2
3
4
5
6
7
// Add a custom property
model.add({
  name: 'testModel',
  version: 1,
  schema: { /* ... */ },
  foo: 'bar'
});

Now, anywhere the testModel is used (even in the adapter) the property foo will be available.

Some adapters even require this, for example the MySQL Adapter needs the property tableName.

Extending the Adapter

All adapters include an extend method. This allows for easily extending custom methods:

1
2
3
4
5
import nedb from 'modli-nedb';

nedb.extend('myCustomFn', () => {
  return 'something';
});

Now when use‘d with a model the myCustomFn method will be available.

Extending As Needed

Not only does the adapter extend method allow for creating custom methods at the adapter level, but even after a datasource object has been generated, extend allows adding methods specific to that model + adapter combination.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Imports
import { model, adapter, use, Joi } from 'modli';
import nedb from 'modli-nedb';

// Add the adapter and model
adapter.add({ name: 'testNeDB', /* ... */ });
model.add({ name: 'testModel', /* ... */ });

// Use the model + adapter combo
const myDatasource = use('testModel', 'testNeDB');

// Extend the datasource
myDatasource.extend('myCustomFn', () => {
  /* ... */
});
Contribute

Contributing

The Modli project was started by the talented team of developers at TechnologyAdvice to solve a problem. We had multiple data sources and multiple methods for interacting.

We believe highly in the benefits of open source software and while we use Modli internally every day we want to make it available to everyone.

If you’d like to help out please fork and then submit pull requests on any of the repo’s, especially the adapters :)

We’re sticklers for clean, readable, and maintainable code so we hope that you will find the code easy to work with. Feel free to submit issues to the core or any of the adapters.