@riktajs/typeorm
v0.11.5
Published
TypeORM integration for Rikta Framework with lifecycle-based connection management
Maintainers
Readme
@riktajs/typeorm
TypeORM integration for Rikta Framework with lifecycle-based connection management
Features
- 🔌 Automatic Connection Management - DataSource initializes on app start, closes on shutdown
- 💉 Dependency Injection - Inject
DataSourceandEntityManagervia@Autowired - ⚙️ Programmatic Configuration - Configure via
createTypeOrmProvider()function - 🔄 Lifecycle Hooks - Uses Rikta's
OnProviderInitandOnProviderDestroy - 📦 Re-exported Decorators - Import TypeORM decorators directly from this package
- 🎯 Type-safe - Full TypeScript support
Installation
npm install @riktajs/typeorm typeormYou'll also need a database driver:
# PostgreSQL
npm install pg
# MySQL / MariaDB
npm install mysql2
# SQLite
npm install better-sqlite3
# Microsoft SQL Server
npm install mssqlPeer Dependencies
@riktajs/core>= 0.4.2typeorm>= 0.3.0reflect-metadata>= 0.2.0
Quick Start
1. Create an Entity
import { Entity, Column, PrimaryGeneratedColumn } from '@riktajs/typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id!: number;
@Column()
name!: string;
@Column({ unique: true })
email!: string;
@Column({ default: true })
isActive!: boolean;
}2. Create a Service
import { Injectable, Autowired } from '@riktajs/core';
import { TYPEORM_DATA_SOURCE } from '@riktajs/typeorm';
import type { DataSource } from '@riktajs/typeorm';
import { User } from './entities/user.entity';
@Injectable()
export class UserService {
@Autowired(TYPEORM_DATA_SOURCE)
private dataSource!: DataSource;
async findAll(): Promise<User[]> {
return this.dataSource.getRepository(User).find();
}
async findById(id: number): Promise<User | null> {
return this.dataSource.getRepository(User).findOneBy({ id });
}
async create(name: string, email: string): Promise<User> {
const user = new User();
user.name = name;
user.email = email;
return this.dataSource.getRepository(User).save(user);
}
}3. Use in a Controller
import { Controller, Get, Post, Body, Autowired } from '@riktajs/core';
import { UserService } from './user.service';
@Controller('/users')
export class UserController {
@Autowired()
private userService!: UserService;
@Get('/')
async listUsers() {
return this.userService.findAll();
}
@Post('/')
async createUser(@Body() body: { name: string; email: string }) {
return this.userService.create(body.name, body.email);
}
}4. Bootstrap the Application
import 'reflect-metadata';
import { Rikta } from '@riktajs/core';
import { initializeTypeOrm } from '@riktajs/typeorm';
import { User } from './entities/user.entity';
async function bootstrap() {
// Initialize TypeORM (automatically connects and registers in DI container)
const { installCleanup } = await initializeTypeOrm({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'admin',
password: 'secret',
database: 'myapp',
entities: [User], // Register your entities
synchronize: false, // Set to true only in development
logging: true,
});
const app = await Rikta.create({
port: 3000,
});
// Install automatic cleanup on app shutdown
installCleanup(app);
await app.listen();
}
bootstrap();The installCleanup(app) call ensures that TypeORM connections are automatically closed when app.close() is called or when the process terminates gracefully.
If you need more control, you can initialize manually:
import 'reflect-metadata';
import { Rikta, Container } from '@riktajs/core';
import { createTypeOrmProvider, TYPEORM_DATA_SOURCE, TYPEORM_ENTITY_MANAGER } from '@riktajs/typeorm';
import { User } from './entities/user.entity';
async function bootstrap() {
// Create and configure the TypeORM provider
const typeormProvider = createTypeOrmProvider({
dataSourceOptions: {
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'admin',
password: 'secret',
database: 'myapp',
entities: [User], // Register your entities
synchronize: false, // Set to true only in development
logging: true,
},
});
// Initialize the provider (connects to database)
await typeormProvider.onProviderInit();
// Register DataSource in the container so services can inject it
const container = Container.getInstance();
container.registerValue(TYPEORM_DATA_SOURCE, typeormProvider.getDataSource());
container.registerValue(TYPEORM_ENTITY_MANAGER, typeormProvider.getEntityManager());
const app = await Rikta.create({
port: 3000,
});
await app.listen();
// Cleanup on shutdown
process.on('SIGTERM', async () => {
await typeormProvider.onProviderDestroy();
await app.close();
});
}
bootstrap();Configuration
All configuration must be provided programmatically through initializeTypeOrm() or createTypeOrmProvider().
This ensures type safety and makes it explicit which entities and options are being used.
Basic Configuration
import { initializeTypeOrm } from '@riktajs/typeorm';
import { User } from './entities/user.entity';
const { installCleanup } = await initializeTypeOrm({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'admin',
password: 'secret',
database: 'myapp',
entities: [User], // Always required
synchronize: false, // Set to true only in development!
logging: true,
});
// Don't forget to call installCleanup(app) after creating your Rikta appUsing Environment Variables
You can still use environment variables by reading them in your bootstrap code:
const { installCleanup } = await initializeTypeOrm({
type: process.env.DB_TYPE as any,
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
entities: [User, Post, Comment],
synchronize: process.env.NODE_ENV === 'development',
logging: process.env.NODE_ENV === 'development',
},
});Multiple Entities
Use an array to register multiple entities:
import { User } from './entities/user.entity';
import { Post } from './entities/post.entity';
import { Comment } from './entities/comment.entity';
await initializeTypeOrm({
// ... other options
entities: [User, Post, Comment],
});Entity Auto-Discovery with Glob Patterns
For larger projects, use glob patterns to auto-discover entities:
const typeormProvider = createTypeOrmProvider({
dataSourceOptions: {
// ... other options
entities: ['src/entities/**/*.entity.{ts,js}'],
// In production build:
// entities: ['dist/entities/**/*.entity.js'],
},
});Injection Tokens
| Token | Type | Description |
|-------|------|-------------|
| TYPEORM_DATA_SOURCE | DataSource | The TypeORM DataSource instance |
| TYPEORM_ENTITY_MANAGER | EntityManager | The EntityManager for the default connection |
Using EntityManager
import { Injectable, Autowired } from '@riktajs/core';
import { TYPEORM_ENTITY_MANAGER } from '@riktajs/typeorm';
import type { EntityManager } from '@riktajs/typeorm';
@Injectable()
export class TransactionService {
@Autowired(TYPEORM_ENTITY_MANAGER)
private entityManager!: EntityManager;
async transferFunds(fromId: number, toId: number, amount: number) {
await this.entityManager.transaction(async (manager) => {
// All operations here run in a transaction
await manager.decrement(Account, { id: fromId }, 'balance', amount);
await manager.increment(Account, { id: toId }, 'balance', amount);
});
}
}Lifecycle
The TypeOrmProvider integrates with Rikta's lifecycle system:
Rikta.create()
│
├─ Initialize providers (sorted by priority)
│ └─ TypeOrmProvider (priority: 100)
│ ├─ Build DataSourceOptions
│ ├─ Create DataSource
│ ├─ Call dataSource.initialize()
│ └─ Register in DI container
│
├─ ... other providers ...
│
app.close()
│
└─ Destroy providers (reverse order)
└─ TypeOrmProvider
└─ Call dataSource.destroy()Priority
TypeOrmProvider has a priority of 100, ensuring the database connection is established before other services that depend on it.
Re-exported Decorators
For convenience, this package re-exports commonly used TypeORM decorators:
import {
// Entity
Entity,
ViewEntity,
// Columns
Column,
PrimaryColumn,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
VersionColumn,
// Relations
OneToOne,
OneToMany,
ManyToOne,
ManyToMany,
JoinColumn,
JoinTable,
// Indexes
Index,
Unique,
// Subscribers
EventSubscriber,
BeforeInsert,
AfterInsert,
BeforeUpdate,
AfterUpdate,
} from '@riktajs/typeorm';Advanced Usage
Custom Repository Pattern
import { Injectable } from '@riktajs/core';
import { TYPEORM_DATA_SOURCE } from '@riktajs/typeorm';
import type { DataSource, Repository } from '@riktajs/typeorm';
import { User } from './entities/user.entity';
@Injectable()
export class UserRepository {
private repository!: Repository<User>;
@Autowired(TYPEORM_DATA_SOURCE)
set dataSource(ds: DataSource) {
this.repository = ds.getRepository(User);
}
async findActive(): Promise<User[]> {
return this.repository.find({
where: { isActive: true },
});
}
async findByEmail(email: string): Promise<User | null> {
return this.repository.findOneBy({ email });
}
}Query Builder
@Injectable()
export class ReportService {
@Autowired(TYPEORM_DATA_SOURCE)
private dataSource!: DataSource;
async getUserStats() {
return this.dataSource
.getRepository(User)
.createQueryBuilder('user')
.select('user.isActive', 'status')
.addSelect('COUNT(*)', 'count')
.groupBy('user.isActive')
.getRawMany();
}
}Retry Configuration
For unstable connections, configure retry behavior:
const typeormProvider = createTypeOrmProvider({
dataSourceOptions: {
type: 'postgres',
host: 'db.example.com',
port: 5432,
username: 'admin',
password: 'secret',
database: 'myapp',
entities: [User],
},
retryAttempts: 3,
retryDelay: 5000, // 5 seconds
});Multiple DataSources
Connect to multiple databases using named providers:
import {
createNamedTypeOrmProvider,
getDataSourceToken,
} from '@riktajs/typeorm';
import { User, Post } from './entities/main';
import { Event, Metric } from './entities/analytics';
// Create providers for each database
const mainDb = createNamedTypeOrmProvider('main', {
type: 'postgres',
host: 'main-db.example.com',
database: 'main',
entities: [User, Post],
});
const analyticsDb = createNamedTypeOrmProvider('analytics', {
type: 'postgres',
host: 'analytics-db.example.com',
database: 'analytics',
entities: [Event, Metric],
});
// Use both providers in Rikta
const app = await Rikta.create({
port: 3000,
providers: [mainDb, analyticsDb],
});Inject named datasources in your services:
import { Injectable, Autowired } from '@riktajs/core';
import { getDataSourceToken } from '@riktajs/typeorm';
import type { DataSource } from '@riktajs/typeorm';
// Get tokens for named connections
const MAIN_DS = getDataSourceToken('main');
const ANALYTICS_DS = getDataSourceToken('analytics');
@Injectable()
class AnalyticsService {
@Autowired(ANALYTICS_DS)
private analyticsDs!: DataSource;
@Autowired(MAIN_DS)
private mainDs!: DataSource;
async trackEvent(userId: number, event: string) {
// Get user from main database
const user = await this.mainDs.getRepository(User).findOneBy({ id: userId });
// Store event in analytics database
await this.analyticsDs.getRepository(Event).save({
userId: user?.id,
event,
timestamp: new Date(),
});
}
}Available functions for managing multiple datasources:
| Function | Description |
|----------|-------------|
| createNamedTypeOrmProvider(name, options) | Create a named provider |
| getDataSourceToken(name) | Get injection token for a named DataSource |
| getEntityManagerToken(name) | Get injection token for a named EntityManager |
| getTypeOrmProvider(name) | Get a registered provider by name |
| getAllTypeOrmProviders() | Get all registered providers |
| initializeAllTypeOrmProviders() | Initialize all registered providers |
| destroyAllTypeOrmProviders() | Close all connections |
Testing
Using SQLite for Tests
import { createTypeOrmProvider } from '@riktajs/typeorm';
const testProvider = createTypeOrmProvider({
type: 'better-sqlite3',
database: ':memory:',
synchronize: true,
entities: [User, Post],
});
beforeAll(async () => {
await testProvider.onProviderInit();
});
afterAll(async () => {
await testProvider.onProviderDestroy();
});Troubleshooting
"DataSource is not initialized"
This error occurs when you try to use the DataSource before it's been initialized. Make sure:
- Your service is decorated with
@Injectable() - You're using
@Autowired(TYPEORM_DATA_SOURCE)to inject the DataSource - You've provided
dataSourceOptionswhen creating the TypeORM provider - The TypeORM provider is included in the
providersarray when creating your Rikta app
"No metadata for Entity was found"
This error means TypeORM doesn't know about your entity. Make sure:
- You've registered the entity in the
entitiesarray ofdataSourceOptions - You're importing the entity file before using it
- The entity class is decorated with
@Entity()
// ✅ Correct
const typeormProvider = createTypeOrmProvider({
dataSourceOptions: {
type: 'postgres',
// ... other options
entities: [User, Post, Comment], // Register your entities here
},
});Connection Refused
Check your database configuration:
// Verify your connection details
const typeormProvider = createTypeOrmProvider({
dataSourceOptions: {
type: 'postgres',
host: 'localhost', // Is this correct?
port: 5432, // Is the port correct?
username: 'admin', // Valid credentials?
password: 'secret',
database: 'myapp', // Does this database exist?
},
});Schema Synchronization Issues
⚠️ Warning: Never use synchronize: true in production! Use migrations instead.
# Generate a migration
npx typeorm migration:generate -d ./data-source.ts ./migrations/CreateUsers
# Run migrations
npx typeorm migration:run -d ./data-source.tsAPI Reference
TypeOrmProvider
| Method | Description |
|--------|-------------|
| onProviderInit() | Lifecycle hook - initializes the DataSource |
| onProviderDestroy() | Lifecycle hook - closes the DataSource |
| getDataSource() | Returns the initialized DataSource |
| getEntityManager() | Returns the EntityManager |
| isConnected() | Returns true if connected |
TypeOrmConfigProvider
| Property | Type | Description |
|----------|------|-------------|
| type | DatabaseType | Database type |
| host | string? | Database host |
| port | number? | Database port |
| username | string? | Username |
| password | string? | Password |
| database | string? | Database name |
| synchronize | boolean | Schema sync |
| logging | boolean | Query logging |
| Method | Description |
|--------|-------------|
| toConfig() | Returns a TypeOrmConfig object |
| isFileBased() | Returns true for SQLite |
| getConnectionString() | Returns connection URL |
License
MIT
