Active Record
The Active Record pattern allows you to interact with the database directly through your entity classes. Each entity instance represents a row in the database, and static methods on the class allow you to query the table.
When to Use It
Choose Active Record when you want the entity itself to own persistence behavior.
This pattern is appropriate when:
- you want very direct CRUD code
- you want to call
User.find()anduser.save() - you are comfortable coupling persistence logic to the entity class
If you prefer repositories and service-layer orchestration, use the Repository pattern instead. In that model, extending BaseEntity is not required.
Defining an Active Record Entity
To use the Active Record pattern, your entities must extend the BaseEntity class.
import { Entity, PrimaryKey, Property, BaseEntity } from '@carno.js/orm';
@Entity()
export class User extends BaseEntity {
@PrimaryKey()
id: number;
@Property()
name: string;
@Property()
email: string;
@Property()
isActive: boolean;
}
What You Get From BaseEntity
By extending BaseEntity, the entity gains:
- static methods like
find,findOne,findAllandcreate - instance methods like
save,removeandisPersisted() - dirty tracking for
save()on already-loaded instances
These capabilities belong to Active Record. They are not part of the Repository-only model.
Creating and Saving
You can create a new instance of an entity, set its properties, and save it to the database.
const user = new User();
user.name = 'John Doe';
user.email = 'john@example.com';
user.isActive = true;
await user.save(); // Inserts the user into the database
console.log(user.id); // ID is automatically populated
Alternatively, you can use the static create method to create and save in one step:
const user = await User.create({
name: 'Jane Doe',
email: 'jane@example.com',
isActive: true
});
Reading Entities
The BaseEntity provides several static methods to query data.
Find All
// Find all users
const users = await User.findAll({});
// Find all active users
const activeUsers = await User.find({ isActive: true });
Find One
// Find a user by email
const user = await User.findOne({ email: 'john@example.com' });
// Find a user or throw an error if not found
const userOrFail = await User.findOneOrFail({ email: 'john@example.com' });
Advanced Finding
You can pass options to filter, sort, and limit results. See Querying & Operators for a full list of supported filters.
const users = await User.find(
{ isActive: true }, // Where clause
{
orderBy: { name: 'ASC' },
limit: 10,
offset: 0
}
);
Updating Entities
To update a loaded entity, fetch it, modify its properties, and call save().
const user = await User.findOneOrFail({ id: 1 });
user.name = 'Updated Name';
await user.save(); // Updates the record
For bulk updates, Active Record also exposes a static update(where, data).
This is the Active Record equivalent of doing a repository bulk update without first loading each entity instance.
await User.update(
{ isActive: true },
{ isActive: false }
);
You can also use computed updates with expr(...).
import { expr } from '@carno.js/orm';
await User.update(
{ id: 1 },
{ experience: expr((prev) => prev.plus(50)) }
);
Why use this:
- when the update affects many rows
- when the new value depends on the current database value
- when you want to avoid
find -> mutate -> savefor a simple arithmetic change
What it turns into:
UPDATE "user" as u1
SET experience = experience + 50
WHERE (u1.id = 1)
prev is not the in-memory property value from an entity instance.
It is a builder for the current SQL column value, so prev.plus(50) becomes column = column + 50.
Supported arithmetic helpers:
prev.plus(value)prev.minus(value)prev.times(value)prev.div(value)
Current limitation:
- Use the explicit helper methods above.
- The ORM does not parse arbitrary JavaScript like
prev => prev + 50.
Deleting Entities
You can delete a loaded entity instance with remove().
const user = await User.findOneOrFail({ id: 1 });
await user.remove();
For bulk deletion, use the static delete(where).
await User.delete({ id: 1 });
That becomes a direct SQL delete similar to:
DELETE FROM "user" AS u1
WHERE (u1.id = 1)
If deletion through repository fits your code organization better, it is fine to mix Active Record entities with repositories. What matters is that BaseEntity is required only for the Active Record API itself.
Checking Persistence
You can check if an entity instance is already saved in the database using isPersisted().
const user = new User();
console.log(user.isPersisted()); // false
await user.save();
console.log(user.isPersisted()); // true