Skip to content

Persistent Layer ​

The persistent layer manages data using Drizzle ORM for type-safe database access and the Repository pattern for data abstraction.

Connectors

This page and the ones below it focus on the PostgreSQL connector (Drizzle + relational tables), the default and most common engine. The persistence layer also ships a SQLite connector (see SQLite) and a typesense connector for search (see Search & Typesense). All three share the same engine-neutral AbstractRepository/AbstractDataSource/AbstractEntity contracts - see Connectors for the architecture.

Architecture Overview ​

┌─────────────────────────────────────────────────────────┐
│                     Application                          │
├─────────────────────────────────────────────────────────┤
│  Controllers  →  Services  →  Repositories  →  Database │
└─────────────────────────────────────────────────────────┘
                                     ▲
                                     │
                    ┌────────────────┴────────────────┐
                    │                                 │
              ┌─────┴─────┐                   ┌───────┴───────┐
              │  Models   │                   │  DataSources  │
              │ (Schema)  │                   │ (Connection)  │
              └───────────┘                   └───────────────┘

Core Components ​

ComponentDescriptionLearn More
ModelsDefine data structure with Drizzle schemas and relationsModels Guide
DataSourcesManage database connections with auto-discoveryDataSources Guide
RepositoriesProvide type-safe CRUD operationsRepositories Guide
TransactionsHandle atomic multi-step operations (both SQL engines)Transactions Guide
Search & TypesenseFull-text/faceted search over documentsSearch & Typesense Guide
PGlitePostgres compiled to WebAssembly, running in-processPGlite Guide
SQLiteThe second SQL engine, via libsqlSQLite Guide

Quick Example ​

typescript
// 1. Define a Model: a Drizzle table, then the model class built from it
export const userTable = pgTable('User', {
  ...generateIdColumnDefs({ id: { dataType: 'string' } }),
  name: text('name').notNull(),
  email: text('email').notNull(),
});

@model({ type: 'entity' })
export class User extends ModelFactory.defineEntity({ table: userTable }) {}

// 2. Create a DataSource
@datasource({ driver: NodePostgresDriver })
export class PostgresDataSource extends BasePostgresDataSource<IDataSourceConfigs> {
  constructor() {
    super({
      name: PostgresDataSource.name,
      config: {
        host: process.env.APP_ENV_POSTGRES_HOST ?? 'localhost',
        port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
        database: process.env.APP_ENV_POSTGRES_DATABASE ?? 'mydb',
        user: process.env.APP_ENV_POSTGRES_USERNAME ?? 'postgres',
        password: process.env.APP_ENV_POSTGRES_PASSWORD ?? '',
      },
    });
  }

  override configure(): ValueOrPromise<void> {
    this.client = new Pool(this.settings); // NodePostgresDriver above wires the driver + connector
  }

  override getConnectionString(): ValueOrPromise<string> {
    const { host, port, user, password, database } = this.settings;
    return `postgresql://${user}:${password}@${host}:${port}/${database}`;
  }
}

// 3. Create a Repository
@repository({ model: User, dataSource: PostgresDataSource })
export class UserRepository extends DefaultCRUDRepository<typeof userTable> {
  async findByEmail(opts: { email: string }) {
    return this.findOne({ filter: { where: { email: opts.email } } });
  }
}

// 4. Register through the application config
import AutoArtifacts from '@venizia/ignis-boot/auto';

export const configs: IApplicationConfigs = {
  path: { base: '/api', isStrict: true },
  artifacts: AutoArtifacts,
};

Next Steps ​

  1. Models - Learn how to define your data structure
  2. DataSources - Configure database connections
  3. Repositories - Master CRUD operations and queries
  4. Transactions - Handle atomic operations

Deep Dive: See Repository Reference for advanced filtering, relations, and operators.

See Also ​