Skip to content

How to Use TypeORM with Capacitor and SQLite

Many developers already use TypeORM on the backend to manage databases with TypeScript decorators and a familiar repository pattern. The good news: the same approach works in Capacitor apps too. The SQLite plugin ships with a built-in SQLiteConnection class that plugs directly into TypeORM's DataSource — no additional adapter package required. This guide walks you through the complete setup, from defining entities to running queries and managing migrations.

What is TypeORM?

TypeORM is one of the most widely used ORMs in the TypeScript ecosystem. It takes a decorator-based approach to database modeling: you define your tables as classes, annotate columns and relationships with decorators, and interact with data through repositories and query builders.

Here's why it pairs well with Capacitor apps:

  • Decorator-based entities — Tables are modeled as regular TypeScript classes. Columns, primary keys, and relationships are declared with decorators like @Column() and @ManyToOne().
  • Repository pattern — Each entity gets a repository with built-in methods for common operations (find, save, remove), so you rarely need to write SQL.
  • Automatic schema sync — During development, TypeORM can synchronize your database schema with your entity definitions automatically.
  • Migration support — For production, TypeORM provides a migration system to apply schema changes incrementally.
  • Wide adoption — TypeORM has a large community and extensive documentation, which means answers to most questions are a search away.

Prerequisites

Before you begin, make sure you have a Capacitor project with the SQLite plugin installed. To install the plugin, please refer to the Installation section in the plugin documentation.

Installation

Since the TypeORM driver is included in the SQLite plugin itself, you only need to install TypeORM and its peer dependency:

npm install typeorm reflect-metadata

TypeORM relies on decorators and metadata reflection, so you need to enable both in your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Finally, import reflect-metadata once at the entry point of your app (e.g. main.ts), before any other imports:

import 'reflect-metadata';

Configuring the DataSource

TypeORM uses a DataSource to manage the database connection. To connect it to the SQLite plugin, pass an SQLiteConnection instance as the driver:

import { Sqlite, SQLiteConnection } from '@capawesome-team/capacitor-sqlite';
import { DataSource } from 'typeorm';

const AppDataSource = new DataSource({
  type: 'capacitor',
  driver: new SQLiteConnection(Sqlite),
  database: 'my-app',
  entities: [],
  synchronize: true,
  logging: ['error', 'schema'],
  migrationsRun: false,
});

A few things to note here:

  • type: 'capacitor' tells TypeORM to use its built-in Capacitor driver, which delegates database operations to the provided driver instance.
  • driver: new SQLiteConnection(Sqlite) bridges TypeORM to the Capacitor SQLite plugin. The SQLiteConnection class handles opening, closing, and routing queries to the correct database.
  • database is the name used to identify the database file.
  • synchronize: true automatically creates and updates tables based on your entities. This is convenient during development but should be disabled in production.
  • migrationsRun: false is required when using the capacitor type.

To initialize the connection when your app starts:

await AppDataSource.initialize();

Defining Entities

TypeORM models database tables as classes decorated with @Entity(). Each property that maps to a column is annotated with a decorator like @PrimaryGeneratedColumn() or @Column().

import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  ManyToOne,
  OneToMany,
  CreateDateColumn,
} from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column('text')
  name!: string;

  @Column({ type: 'text', unique: true })
  email!: string;

  @CreateDateColumn()
  createdAt!: Date;

  @OneToMany(() => Post, (post) => post.author)
  posts!: Post[];
}

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column('text')
  title!: string;

  @Column({ type: 'text', nullable: true })
  content!: string | null;

  @ManyToOne(() => User, (user) => user.posts)
  author!: User;
}

A few things to note:

  • @PrimaryGeneratedColumn() creates an auto-incrementing primary key.
  • @Column() accepts a type string or an options object for constraints like unique and nullable.
  • @CreateDateColumn() automatically sets the current timestamp when a row is inserted.
  • @OneToMany() and @ManyToOne() define the relationship between User and Post. TypeORM uses these to generate foreign keys and enable eager/lazy loading.

Don't forget to register your entities in the DataSource configuration:

const AppDataSource = new DataSource({
  // ...
  entities: [User, Post],
});

Working with Repositories

TypeORM's repository pattern provides a high-level API for data access. Each entity gets its own repository with built-in methods for the most common operations.

Insert

const userRepo = AppDataSource.getRepository(User);

const user = userRepo.create({
  name: 'Alice',
  email: 'alice@example.com',
});
await userRepo.save(user);

The create() method instantiates an entity without persisting it. Calling save() writes it to the database and populates the generated id.

Select

// Find all users
const allUsers = await userRepo.find();

// Find with a condition
const user = await userRepo.findOneBy({
  email: 'alice@example.com',
});

// Find with relations
const userWithPosts = await userRepo.findOne({
  where: { id: 1 },
  relations: { posts: true },
});

Update

await userRepo.update({ id: 1 }, { name: 'Bob' });

Delete

await userRepo.delete({ id: 1 });

For more complex queries, TypeORM also offers a QueryBuilder that supports joins, subqueries, and aggregations:

const users = await userRepo
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .where('user.name LIKE :name', { name: '%Ali%' })
  .getMany();

Transactions

When multiple operations need to succeed or fail as a unit, wrap them in a transaction:

await AppDataSource.transaction(async (manager) => {
  const user = manager.create(User, {
    name: 'Alice',
    email: 'alice@example.com',
  });
  await manager.save(user);

  const post = manager.create(Post, {
    title: 'Hello World',
    content: '...',
    author: user,
  });
  await manager.save(post);
});

If any operation inside the callback throws, the entire transaction is rolled back. The manager parameter is a transactional EntityManager — use it instead of individual repositories to ensure all operations run within the same transaction.

Migrations

While synchronize: true is handy during development, it should not be used in production since it can lead to data loss. Instead, use TypeORM's migration system to apply schema changes incrementally.

Writing Migrations

Create a migration class that implements up and down methods:

import { MigrationInterface, QueryRunner } from 'typeorm';

export class CreateUsers1709000000000 implements MigrationInterface {
  async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`
      CREATE TABLE IF NOT EXISTS user (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT NOT NULL UNIQUE,
        createdAt DATETIME DEFAULT (datetime('now'))
      )
    `);
  }

  async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.query(`DROP TABLE IF EXISTS user`);
  }
}

Running Migrations

Register your migrations in the DataSource and run them at startup:

import { CreateUsers1709000000000 } from './migrations/CreateUsers1709000000000';

const AppDataSource = new DataSource({
  type: 'capacitor',
  driver: new SQLiteConnection(Sqlite),
  database: 'my-app',
  entities: [User, Post],
  migrations: [CreateUsers1709000000000],
  synchronize: false,
  migrationsRun: false,
});

await AppDataSource.initialize();
await AppDataSource.runMigrations();

TypeORM tracks which migrations have been applied in a migrations table, so calling runMigrations() repeatedly is safe — only pending migrations are executed.

Stay Updated

Want to stay up to date with the latest features and guides? Subscribe to the Capawesome newsletter.

Subscribe to the Capawesome Newsletter

Conclusion

TypeORM brings its decorator-based entity modeling and repository pattern to Capacitor apps through the built-in SQLiteConnection class in the SQLite plugin. There's no separate adapter to install — just configure a DataSource with type: 'capacitor', define your entities as decorated classes, and use repositories for data access.

If you prefer a lighter, SQL-first approach, check out our guides on Drizzle ORM and Kysely as alternatives. If you have questions or feedback, join the Capawesome Discord server to connect with the community. And subscribe to the Capawesome newsletter to stay updated on the latest news.