Skip to main content

ORM Patterns

Hysteria ORM supports two distinct patterns for working with your database models:

  1. Active Record Pattern - Static methods on model classes (recommended)
  2. Repository Pattern - Using getModelManager() for dependency injection

Active Record Pattern

The Active Record pattern is the recommended approach for most applications. It provides a clean, intuitive API where you call static methods directly on your model classes.

How it Works

In the Active Record pattern, your model classes have static methods that handle database operations. These methods internally use a ModelManager instance but abstract away the complexity.

Example Usage

import { Model, column } from 'hysteria-orm';

class User extends Model {
@column.integer({ primaryKey: true })
declare id: number;

@column()
declare name: string;

@column()
declare email: string;
}

// Active Record Pattern - Static methods on the model
const user = await User.insert({ name: 'John', email: 'john@example.com' });
const users = await User.find({ where: { isActive: true } });
const userById = await User.findOneByPrimaryKey(1);
const query = User.query().where('name', 'like', '%John%').many();

Available Static Methods

MethodDescriptionExample
Model.insert(data)Insert a new recordUser.insert({ name: 'John' })
Model.insertMany(data[])Insert multiple recordsUser.insertMany([{ name: 'John' }, { name: 'Jane' }])
Model.find(options)Find multiple recordsUser.find({ where: { isActive: true } })
Model.findOne(options)Find a single recordUser.findOne({ where: { email: 'john@example.com' } })
Model.findBy(column, value)Find by specific columnUser.findBy('email', 'john@example.com')
Model.findOneBy(column, value)Find one by specific columnUser.findOneBy('email', 'john@example.com')
Model.findOneByPrimaryKey(id)Find by primary keyUser.findOneByPrimaryKey(1)
Model.update(data, options)Update recordsUser.update({ name: 'Johnny' }, { where: { id: 1 } })
Model.delete(options)Delete recordsUser.delete({ where: { isActive: false } })
Model.query()Get query builderUser.query().where('age', '>', 18).many()
Model.all()Get all recordsUser.all()
Model.count(options)Count recordsUser.count({ where: { isActive: true } })
Model.exists(options)Check if records existUser.exists({ where: { email: 'john@example.com' } })

Benefits

  • Clean API: Simple, intuitive method calls
  • Type Safety: Full TypeScript support with proper typing
  • Model Awareness: Automatic handling of decorators, relations, and hooks
  • Less Boilerplate: No need to manage ModelManager instances
  • Familiar: Similar to other popular ORMs like TypeORM
  • Model Embedding Support: Can be used with embedded models for even cleaner syntax

Model Embedding with Active Record

Model embedding allows you to attach models directly to a SQL data source instance, providing an even cleaner API:

const sql = new SqlDataSource({
type: 'postgres',
models: { user: User, post: Post },
});
await sql.connect();

// Access models directly through the data source
const user = await sql.user.insert({ name: 'John', email: 'john@example.com' });
const users = await sql.user.find({ where: { isActive: true } });

For detailed documentation, see Model Embedding.

Repository Pattern (getModelManager)

The Repository pattern provides more control and is useful for dependency injection, testing, or when you need explicit control over the data source connection.

How it Works

Instead of using static methods, you get a ModelManager instance from the data source and use its methods directly.

Example Usage

import { SqlDataSource, Model, column } from 'hysteria-orm';

class User extends Model {
@column.integer({ primaryKey: true })
declare id: number;

@column()
declare name: string;

@column()
declare email: string;
}

// Connect first
const sql = new SqlDataSource({ type: 'postgres', ... });
await sql.connect();

// Repository Pattern - Using getModelManager
const userManager = sql.getModelManager(User);

const user = await userManager.insert({ name: 'John', email: 'john@example.com' });
const users = await userManager.find({ where: { isActive: true } });
const userById = await userManager.findOneByPrimaryKey(1);
const query = userManager.query().where('name', 'like', '%John%').many();

When to Use Repository Pattern

The Repository pattern is useful when you need:

  1. Dependency Injection: Pass the manager to services or controllers
  2. Testing: Mock the manager for unit tests
  3. Multiple Connections: Use different data sources for the same model
  4. Explicit Control: Want to manage the data source connection explicitly

Example with Dependency Injection

// Service class that accepts a ModelManager
class UserService {
constructor(private userManager: ModelManager<User>) {}

async createUser(userData: Partial<User>) {
return await this.userManager.insert(userData);
}

async findActiveUsers() {
return await this.userManager.find({
where: { isActive: true }
});
}
}

// Usage
const userManager = sql.getModelManager(User);
const userService = new UserService(userManager);
const users = await userService.findActiveUsers();

Example with Custom Connection

// Using a secondary connection
const secondarySql = await SqlDataSource.connectToSecondarySource({
type: 'postgres',
host: 'read-replica.example.com',
// ... other config
});

const readOnlyUserManager = secondarySql.getModelManager(User);
const users = await readOnlyUserManager.find({ where: { isActive: true } });

Pattern Comparison

FeatureActive RecordModel EmbeddingRepository Pattern
API Simplicity✅ Simple✅ Cleanest⚠️ Verbose
Type Safety✅ Full✅ Full✅ Full
Dependency Injection❌ Limited✅ Perfect✅ Perfect
Testing⚠️ Harder✅ Easy✅ Easy
Multiple Connections⚠️ Limited✅ Full✅ Full

When to Use Each Pattern

PatternBest For
Active RecordSimple apps, quick prototypes, default connection
Model EmbeddingService architecture, dependency injection, microservices
Repository PatternEnterprise apps, fine-grained control, multiple connections

Migrating Between Patterns

All patterns use the same underlying ModelManager, so you can switch as your application grows:

// Start with Active Record
const user = await User.insert({ name: 'John' });

// Upgrade to Model Embedding
const sql = new SqlDataSource({
type: 'postgres',
models: { user: User },
});

const user = await sql.user.insert({ name: 'John' });

// Or use Repository Pattern
const userManager = sql.getModelManager(User);
const user = await userManager.insert({ name: 'John' });

Next: Defining Models