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 typesense connector for search (see Search & Typesense). Both 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 (PostgreSQL connector only)Transactions Guide
Search & TypesenseFull-text/faceted search over documentsSearch & Typesense Guide

Quick Example

typescript
// 1. Define a Model
@model({ type: 'entity' })
export class User extends BasePostgresEntity<typeof User.schema> {
  static override schema = pgTable('User', {
    ...generateIdColumnDefs({ id: { dataType: 'string' } }),
    name: text('name').notNull(),
    email: text('email').notNull(),
  });

  static override relations = () => [];
}

// 2. Create a DataSource
@datasource({ driver: NodePostgresDriver })
export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
  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 User.schema> {
  async findByEmail(opts: { email: string }) {
    return this.findOne({ filter: { where: { email: opts.email } } });
  }
}

// 4. Use in Application
export class Application extends BaseApplication {
  preConfigure() {
    this.dataSource(PostgresDataSource);
    this.repository(UserRepository);
  }
}

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