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
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.
| // 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:
| 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:
| 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:
| // 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:
| // 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:
| // 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.
| // 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:
| 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.
| // 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.