Security News
Cloudflare Adds Security.txt Setup Wizard
Cloudflare has launched a setup wizard allowing users to easily create and manage a security.txt file for vulnerability disclosure on their websites.
objection
Advanced tools
Objection.js is an ORM (Object-Relational Mapper) for Node.js, built on top of the SQL query builder Knex.js. It aims to provide a powerful and flexible way to interact with SQL databases using JavaScript, while maintaining a clean and intuitive API.
Model Definition
Defines a model class that maps to a database table. In this example, the `Person` class represents the `persons` table in the database.
const { Model } = require('objection');
class Person extends Model {
static get tableName() {
return 'persons';
}
}
Querying
Performs a query to fetch all persons older than 30 years. Objection.js provides a fluent API for building SQL queries.
const people = await Person.query().where('age', '>', 30);
Relations
Defines relationships between models. In this example, a `Person` can have many `Animal` pets, establishing a one-to-many relationship.
class Person extends Model {
static get tableName() {
return 'persons';
}
static get relationMappings() {
const Animal = require('./Animal');
return {
pets: {
relation: Model.HasManyRelation,
modelClass: Animal,
join: {
from: 'persons.id',
to: 'animals.ownerId'
}
}
};
}
}
Eager Loading
Fetches related data in a single query. This example fetches persons along with their pets using eager loading.
const peopleWithPets = await Person.query().withGraphFetched('pets');
Validation
Defines a JSON schema for model validation. This example ensures that `firstName` and `lastName` are required fields and must be strings of a certain length.
class Person extends Model {
static get jsonSchema() {
return {
type: 'object',
required: ['firstName', 'lastName'],
properties: {
id: { type: 'integer' },
firstName: { type: 'string', minLength: 1, maxLength: 255 },
lastName: { type: 'string', minLength: 1, maxLength: 255 }
}
};
}
}
Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite, and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication, and more. Compared to Objection.js, Sequelize has a more extensive feature set but can be more complex to use.
TypeORM is an ORM for TypeScript and JavaScript (ES7, ES6, ES5). It supports many database systems including MySQL, MariaDB, PostgreSQL, SQLite, and more. TypeORM is known for its strong TypeScript support and decorators, making it a good choice for TypeScript projects. It offers similar functionalities to Objection.js but with a different API style.
Bookshelf.js is a JavaScript ORM for Node.js, built on top of the Knex.js SQL query builder. It provides a simple and straightforward way to interact with SQL databases. Bookshelf.js is similar to Objection.js in that both are built on Knex.js, but Bookshelf.js has a more minimalistic approach.
Objection.js is a Node.js ORM built around the wonderful SQL query builder knex. All databases supported by knex are supported by objection.js. SQLite3, Postgres and MySQL are fully tested.
What objection.js gives you:
What objection.js doesn't give you:
Objection.js uses Promises and coding practices that make it ready for future. You can already use things like ES7 async/await and ES6 classes using a transpiler such as Babel. Check out our ES7 example project.
npm install objection
Best way to get started is to use one of the example projects:
git clone git@github.com:Vincit/objection.js.git objection
cd objection/examples/express
npm install
# We use knex for migrations in this example.
npm install knex -g
knex migrate:latest
npm 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/objection.js.git objection
cd objection/examples/express-es7
npm install
# We use knex for migrations in this example.
npm install knex -g
knex migrate:latest
# This runs the Babel transpiler and executes the app.
npm start
Also check out our API documentation and recipe book.
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
.query()
.insert({firstName: 'Jennifer', lastName: 'Lawrence'})
.then(function (jennifer) {
console.log(jennifer instanceof Person); // --> true
console.log(jennifer.firstName); // --> 'Jennifer'
console.log(jennifer.fullName()); // --> 'Jennifer Lawrence'
})
.catch(function (err) {
console.log('oh noes');
});
Fetch all Persons from the database:
Person
.query()
.then(function (persons) {
console.log(persons[0] instanceof Person); // --> true
console.log('there are', persons.length, 'Persons in total');
})
.catch(function (err) {
console.log('oh noes');
});
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
.query()
.where('age', '>', 40)
.andWhere('age', '<', 60)
.andWhere('firstName', 'Jennifer')
.orderBy('lastName')
.then(function (middleAgedJennifers) {
console.log('The last name of the first middle aged Jennifer is');
console.log(middleAgedJennifers[0].lastName);
});
Update models:
Person
.query()
.patch({lastName: 'Dinosaur'});
.where('age', '>', 60)
.then(function (patch) {
console.log('all persons over 60 years old are now dinosaurs');
console.log(patch.lastName); // --> Dinosaur.
})
.catch(function (err) {
console.log(err.stack);
});
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
.query()
.where('firstName', 'Jennifer')
.first()
.then(function (person) {
jennifer = person;
return jennifer
.$relatedQuery('pets')
.where('species', 'dog')
.orderBy('name');
})
.then(function (jennifersDogs) {
console.log(jennifersDogs[0] instanceof Animal); // --> true
console.log(jennifer.pets === jennifersDogs); // --> true
console.log('Jennifer has', jennifersDogs.length, 'dogs');
})
.catch(function (err) {
console.log(err.stack);
});
Insert a related model:
Person
.query()
.where('id', 100)
.first()
.then(function (person) {
return person.$relatedQuery('pets').insert({name: 'Fluffy'});
})
.then(function (fluffy) {
console.log(fully.id);
})
.catch(function (err) {
console.log('something went wrong with finding the person OR inserting the pet');
console.log(err.stack);
});
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
.query()
.eager('pets')
.then(function (persons) {
// Each person has the `.pets` property populated with Animal objects related
// through `pets` relation.
console.log(persons[0].pets[0].name);
console.log(persons[0].pets[0] instanceof Animal); // --> true
});
Fetch multiple relations on multiple levels:
Person
.query()
.eager('[pets, children.[pets, children]]')
.then(function (persons) {
// Each person has the `.pets` property populated with Animal objects related
// through `pets` relation. The `.children` property contains the Person's
// children. Each child also has the `pets` and `children` relations eagerly
// fetched.
console.log(persons[0].pets[0].name);
console.log(persons[1].children[2].pets[1].name);
console.log(persons[1].children[2].children[0].name);
});
Fetch one relation recursively:
Person
.query()
.eager('[pets, children.^]')
.then(function (persons) {
// The children relation is from Person to Person. If we want to fetch the whole
// descendant tree of a person we can just say "fetch this relation recursively"
// using the `.^` notation.
console.log(persons[0].children[0].children[0].children[0].children[0].firstName);
});
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.get('/persons', function (req, res, next) {
Person
.query()
.allowEager('[pets, children.pets]')
.eager(req.query.eager)
.then(function (persons) { res.send(persons); })
.catch(next);
});
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 are started by calling the objection.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.
objection.transaction(Person, Animal, function (Person, Animal) {
return Person
.query()
.insert({firstName: 'Jennifer', lastName: 'Lawrence'})
.then(function () {
return Animal
.query()
.insert({name: 'Scrappy'});
});
}).then(function (scrappy) {
console.log('Jennifer and Scrappy were successfully inserted');
}).catch(function (err) {
console.log('Something went wrong. Neither Jennifer nor Scrappy were inserted');
});
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.
objection.transaction(Person, function (Person) {
return Person
.query()
.insert({firstName: 'Jennifer', lastName: 'Lawrence'})
.then(function (jennifer) {
// This creates a query using the `Animal` model class but we
// don't need to give `Animal` as one of the arguments to the
// transaction function.
return jennifer
.$relatedQuery('pets')
.insert({name: 'Scrappy'});
});
}).then(function (scrappy) {
console.log('Jennifer and Scrappy were successfully inserted');
}).catch(function (err) {
console.log('Something went wrong. Neither Jennifer nor Scrappy were inserted');
});
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 = require('./models/Person');
var Animal = require('./models/Animal');
objection.transaction(Person, function (Person) {
return Person
.query()
.insert({firstName: 'Jennifer', lastName: 'Lawrence'})
.then(function (jennifer) {
// OH NO! This query is executed outside the transaction
// since the `Animal` class is not bound to the transaction.
return Animal
.query()
.insert({name: 'Scrappy'});
});
});
Objection.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
.query()
.insert({
firstName: 'Jennifer',
lastName: 'Lawrence',
age: 24,
address: {
street: 'Somestreet 10',
zipCode: '123456',
city: 'Tampere'
}
})
.then(function (jennifer) {
console.log(jennifer.address.city); // --> Tampere
return Person.query().where('id', jennifer.id);
})
.then(function (jenniferFromDb) {
console.log(jenniferFromDb.address.city); // --> Tampere
})
.catch(function (err) {
console.log('oh noes');
});
JSON schema validation can be enabled by setting the jsonSchema property
of a model class. The validation is ran each time a Model
instance is created. For example all these will trigger
the validation:
Person.fromJson({firstName: 'jennifer', lastName: 'Lawrence'});
Person.query().insert({firstName: 'jennifer', lastName: 'Lawrence'});
Person.query().update({firstName: 'jennifer', lastName: 'Lawrence'}).where('id', 10);
// Patch operation ignores the `required` property of the schema and only validates the
// given properties. This allows a subset of model's properties to be updated.
Person.query().patch({age: 24}).where('age', '<', 24);
You rarely need to call $validate method explicitly, but you
can do it when needed. If validation fails a ValidationError
will be thrown. Since we use Promises, this usually means that a promise will be rejected with an instance of
ValidationError
.
Person.query().insert({firstName: 'jennifer'}).catch(function (err) {
console.log(err instanceof objection.ValidationError); // --> true
console.log(err.data); // --> {lastName: 'required property missing'}
});
See the recipe book for instructions if you want to use some other validation library.
Models are created by inheriting from the Model base class. In objection.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 = require('objection').Model;
function Person() {
Model.apply(this, arguments);
}
Model.extend(Person);
module.exports = Person;
// You can add custom functionality to Models just as you would
// to any javascript class.
Person.prototype.fullName = function () {
return this.firstName + ' ' + this.lastName;
};
// Table name is the only required property.
Person.tableName = 'Person';
// Optional JSON schema. This is not the database schema! Nothing is generated
// based on this. This is only used for validation. Whenever a model instance
// is created it is checked against this schema. http://json-schema.org/.
Person.jsonSchema = {
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.
Person.relationMappings = {
pets: {
relation: Model.OneToManyRelation,
// 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: Model.ManyToManyRelation,
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: Model.OneToManyRelation,
modelClass: Person,
join: {
from: 'Person.id',
to: 'Person.parentId'
}
}
};
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.
FAQs
An SQL-friendly ORM for Node.js
The npm package objection receives a total of 136,826 weekly downloads. As such, objection popularity was classified as popular.
We found that objection demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Cloudflare has launched a setup wizard allowing users to easily create and manage a security.txt file for vulnerability disclosure on their websites.
Security News
The Socket Research team breaks down a malicious npm package targeting the legitimate DOMPurify library. It uses obfuscated code to hide that it is exfiltrating browser and crypto wallet data.
Security News
ENISA’s 2024 report highlights the EU’s top cybersecurity threats, including rising DDoS attacks, ransomware, supply chain vulnerabilities, and weaponized AI.