Waterline ORM
Waterline is an Object-Relational Mapper (ORM) for Node.js, designed to provide a uniform API for interacting with various data stores such as MySQL, PostgreSQL, MongoDB, Redis, and more, through a pluggable adapter system. While it is the default ORM within the Sails.js framework, it can also be used as a standalone library. The current stable version is 0.15.2, although its last publish date was over three years ago. A significant architectural shift occurred from v0.13 onwards, transitioning from callback-based APIs to fully embracing ECMAScript's `async/await` syntax for all query operations. Key differentiators include its consistent interface across diverse data stores, a strong emphasis on modularity and testability, and an ActiveRecord-inspired pattern tailored for modern JavaScript development, simplifying data persistence by abstracting database specifics behind declarative model definitions.
Common errors
-
TypeError: callback is not a function
cause Attempting to use the old callback pattern (e.g., `.exec(cb)`) on a query after Waterline's transition to `async/await` (from v0.13).fixRemove `.exec(cb)` and use `await` before the query, or chain `.then().catch()` to handle the promise. Remember to add `.fetch()` for `create`, `update`, and `destroy` operations to retrieve the record(s). -
Error: Adapter 'my-adapter' not registered.
cause The specified adapter was not correctly installed or registered with the Waterline instance during initialization. Waterline does not ship with adapters.fixInstall the required adapter package (e.g., `npm install sails-mysql`) and ensure it's imported and explicitly registered in the `config.datastores` object before `waterline.initialize()`. -
ReferenceError: MyModel is not defined
cause Trying to access a Waterline model (e.g., `MyModel.find()`) directly without proper initialization or scoping. Models are exposed via the initialized Waterline instance.fixEnsure Waterline is fully initialized (`await waterline.initialize(config)`), and then access your models through the returned `collections` object, typically like `const MyModel = collections.mymodel;`. -
Error: A record with that unique key already exists.
cause Attempting to create or update a record with a value that violates a uniqueness constraint defined in the model's attributes (e.g., a duplicate email for a `unique: true` field).fixImplement error handling for uniqueness violations. Before creating, check if the record exists, or catch the specific error type after the operation and handle it gracefully (e.g., notify the user).
Warnings
- breaking Starting with Waterline v0.13, the API transitioned from callback-based methods to `async/await` (promises). Existing code relying on `.exec(callback)` will break.
- breaking Waterline v0.11.0 removed the second argument from `.save()` commands that previously returned the newly updated data. This change was for performance optimization.
- breaking Waterline v0.12.2 fixed critical issues with compatibility in `alter` auto-migrations which were causing corrupted data, especially in SQL adapters. Older versions might have led to data integrity problems.
- gotcha Sails.js framework versions have specific Waterline compatibility. Sails v0.12 uses Waterline 0.11.x, whereas Sails v1.0 and later use Waterline v0.13+ (which includes the `await` syntax).
- gotcha Issues were reported and fixed in `v0.12.1` and `v0.11.2` related to searching by `id` in schemaless mode, which could lead to incorrect results or errors.
- breaking In Waterline v0.13, criteria objects passed into model methods (e.g., `update`, `createEach`) will be mutated in-place for performance. This was not always the case in v0.12. Also, aggregation clauses (`sum`, `average`, `min`, `max`, `groupBy`) are no longer supported in criteria.
Install
-
npm install waterline -
yarn add waterline -
pnpm add waterline
Imports
- Waterline
const Waterline = require('waterline');import { Waterline } from 'waterline'; - Waterline.Collection
import { Collection } from 'waterline';import { Waterline } from 'waterline'; // ... then use Waterline.Collection.extend - Model instances (e.g., User)
import { User } from 'waterline';const User = collections.user;
Quickstart
import { Waterline } from 'waterline';
import DiskAdapter from 'sails-disk'; // Example adapter, install with `npm i sails-disk`
// 1. Initialize Waterline
const waterline = new Waterline();
// 2. Define a Collection (Model)
const UserCollection = Waterline.Collection.extend({
identity: 'user',
datastore: 'default',
primaryKey: 'id',
attributes: {
id: { type: 'number', autoIncrement: true },
name: { type: 'string', required: true },
email: { type: 'string', unique: true },
age: { type: 'number', defaultsTo: 18 },
},
});
// 3. Register the Collection
waterline.registerModel(UserCollection);
// 4. Configure Waterline
const config = {
datastores: {
default: {
adapter: 'sails-disk',
},
},
models: {
migrate: 'alter', // 'safe', 'alter', 'drop'
}
};
async function runWaterlineExample() {
try {
// 5. Initialize the ORM
const { collections, connections } = await waterline.initialize(config);
// Access the User model
const User = collections.user;
// Create a new user
const newUser = await User.create({ name: 'Alice', email: 'alice@example.com' }).fetch();
console.log('Created user:', newUser);
// Find all users
const allUsers = await User.find();
console.log('All users:', allUsers);
// Update a user
const updatedUser = await User.updateOne({ id: newUser.id })
.set({ age: 30 })
.fetch();
console.log('Updated user:', updatedUser);
// Clean up (release connections)
await waterline.teardown();
} catch (err) {
console.error('Waterline error:', err);
}
}
runWaterlineExample();