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:
TypeORM relies on decorators and metadata reflection, so you need to enable both in your tsconfig.json:
Finally, import reflect-metadata once at the entry point of your app (e.g. main.ts), before any other imports:
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 provideddriverinstance.driver: new SQLiteConnection(Sqlite)bridges TypeORM to the Capacitor SQLite plugin. TheSQLiteConnectionclass handles opening, closing, and routing queries to the correct database.databaseis the name used to identify the database file.synchronize: trueautomatically creates and updates tables based on your entities. This is convenient during development but should be disabled in production.migrationsRun: falseis required when using thecapacitortype.
To initialize the connection when your app starts:
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 likeuniqueandnullable.@CreateDateColumn()automatically sets the current timestamp when a row is inserted.@OneToMany()and@ManyToOne()define the relationship betweenUserandPost. TypeORM uses these to generate foreign keys and enable eager/lazy loading.
Don't forget to register your entities in the DataSource configuration:
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¶
Delete¶
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.