Introduction
Moron.js is a Node.js ORM built around the wonderful SQL query builder knex. All databases supported by knex are supported by moron.js. SQLite3, Postgres and MySQL are fully tested.
What moron.js gives you:
- An easy declarative way of defining models and relations between them
- Simple and fun way to fetch, insert, update and delete models using the full power of SQL
- A way to store complex documents as single rows
- Powerful mechanism for loading arbitrarily large trees of relations
- Completely Promise based API
- Simple transactions
- JSON schema validation
What moron.js doesn't give you:
- A custom query DSL. SQL is used everywhere
- Automatic database schema creation and migration. It is useful for the simple things, but usually just gets in your way when doing anything non-trivial. Moron.js leaves the schema related things to you. knex has a great migration tool that we recommend for this job.
Topics
- Installation
- Getting started
- Query examples
- Eager queries
- Transactions
- Documents
- Models
- Testing
- API Documentation
- Recipe book
Installation
npm install moron
Getting started
Best way to get started is to use one of the example projects:
git clone git@github.com:Vincit/moron.js.git moroncd moron/examples/expressnpm install# We use knex for migrations. npm install knex -gknex migrate:latestnpm start
The express
example project is a simple express server. The example-requests.sh
file contains a bunch of curl
commands for you to start playing with the REST API.
cat example-requests.sh
We also have an ES7 version of the express example project. It uses Babel for the ES7 --> ES5 transpiling.
git clone git@github.com:Vincit/moron.js.git moroncd moron/examples/express-es7npm install# We use knex for migrations. npm install knex -gknex migrate:latest# This runs the Babel transpiler and executes the app. npm start
Also our API documentation contains a lot of examples.
Query examples
The Person model used in the examples is defined here.
All queries are started with one of the Model methods query(), $query() or $relatedQuery(). All these methods return a QueryBuilder instance that can be used just like a knex QueryBuilder.
Insert a Person model to the database:
Person ;
Fetch all Persons from the database:
Person ;
The return value of the .query()
method is an instance of QueryBuilder
that has all the methods a knex QueryBuilder has. Here is a simple example that uses some of them:
Person ;
Update models:
Person ; ;
While the static .query()
method can be used to create a query to a whole table .$relatedQuery()
method
can be used to query a single relation. .$relatedQuery()
returns an instance of QueryBuilder
just like the .query()
method.
var jennifer;Person ;
Insert a related model:
Person ;
Eager queries
Okay I said there is no custom DSL but actually we have teeny-tiny one for fetching relations eagerly. The following examples demonstrate how to use it:
Fetch one relation:
Person ;
Fetch multiple relations on multiple levels:
Person ;
Fetch one relation recursively:
Person ;
The expressions can be arbitrarily deep. See the full description here.
Because the eager expressions are strings they can be easily passed for example as a query parameter of an HTTP
request. However, using such expressions opens the whole database through the API. This is not very secure. Therefore
the QueryBuilder has the .allowEager
method.
allowEager can be used to limit the allowed eager expression to a certain subset. Like this:
expressApp;
The example above allows req.query.eager
to be one of 'pets'
, 'children'
, 'children.pets'
, '[pets, children]'
and
'[pets, children.pets]'
. Examples of failing eager expressions are 'movies'
, 'children.children'
and 'notEvenAnExistingRelation'
.
In addition to the .eager
method, relations can be fetched using the loadRelated
and $loadRelated
methods of
Model.
Transactions
Transactions are started by calling the moron.transaction
function. Give all the models you want to use in the transaction as parameters to the transaction
function. The model
classes are bound to a newly created transaction and passed to the callback function. Inside this callback, all queries
started through them take part in the same transaction.
The transaction is committed if the returned Promise is resolved successfully. If the returned Promise is rejected the transaction is rolled back.
moron;
You only need to give the transaction
function the model classes you use explicitly. All the related model classes
are implicitly bound to the same transaction.
moron;
The only way you can mess up with the transactions is if you explicitly start a query using a model class that is not bound to the transaction:
var Person = ;var Animal = ; moron;
Documents
Moron.js makes it easy to store non-flat documents as table rows. All properties of a model that are marked as
objects or arrays in the model's jsonSchema
are automatically converted to JSON strings in the database and
back to objects when read from the database. The database columns for the object properties can be normal
text columns. Postgresql has the json
and jsonb
data types that can be used instead for better performance
and possibility to make queries to the documents.
The address
property of the Person model is defined as an object in the Person.jsonSchema:
Person ;
Models
Models are created by inheriting from the Model base class. In moron.js the inheritance is done as transparently as possible. There is no custom Class abstraction making you wonder what the hell is happening. Just plain old ugly javascript inheritance.
var Model = Model; /** * @override Model * @constructor */ { Model;} Model;moduleexports = Person; // You can add custom functionality to Models just as you would// to any javascript class.Personprototype { return thisfirstName + ' ' + thislastName;}; // Table name is the only required property.PersontableName = 'Person'; // This is not the database schema! Nothing is generated based on this. Whenever a// Person object is created from a JSON object, the JSON is checked against this// schema. For example when you call Person.fromJson({firstName: 'Jennifer'});PersonjsonSchema = type: 'object' required: 'firstName' 'lastName' properties: id: type: 'integer' parentId: type: 'integer' 'null' firstName: type: 'string' minLength: 1 maxLength: 255 lastName: type: 'string' minLength: 1 maxLength: 255 age: type: 'number' address: type: 'object' properties: street: type: 'string' city: type: 'string' zipCode: type: 'string' ; // This object defines the relations to other models.PersonrelationMappings = pets: relation: ModelOneToManyRelation // The related model. This can be either a Model subclass constructor or an // absolute file path to a module that exports one. We use the file path version // here to prevent require loops. modelClass: __dirname + '/Animal' join: from: 'Person.id' to: 'Animal.ownerId' movies: relation: ModelManyToManyRelation modelClass: __dirname + '/Movie' join: from: 'Person.id' // ManyToMany relation needs the `through` object to describe the join table. through: from: 'Person_Movie.personId' to: 'Person_Movie.movieId' to: 'Movie.id' children: relation: ModelOneToManyRelation modelClass: Person join: from: 'Person.id' to: 'Person.parentId' ;
Testing
To run the tests, all you need to do is configure the databases and run npm test
. Check out
this file for the
test database configurations. If you don't want to run the tests against all databases you can
just comment out configurations from the testDatabaseConfigs
list.