Using TypeORM with the capacitor driver type
November 20, 2025 ยท View on GitHub
Some Basics
-
Developers can use the
@capacitor-community/sqliteplugin to create apps for Web, Electron, iOS, or Android using the same code. Almost any framework can be used to develop these apps:- Ionic7/Angular, Ionic7/React, Ionic7/Vue
- Pure Angular, React, Vue3
- SolidJS
- Quasar
- Nuxt3 + Kysely
- Svelte
-
The
typeOrmpackage now has acapacitordriver type that must be used with the@capacitor-community/sqlitecapacitor plugin. -
Apps using the
@capacitor-community/sqlitecapacitor plugin cannot use theCLIof thetypeOrmpackage. Developers trying to do so, will receive a message stating that the "jeep-sqlite element is not present in the DOM". The Web part of the plugin needs the DOM to create and store the database. -
In release 5.6.0, I attempted to propose a workaround by utilizing certain Node.js packages like 'fs', 'os', and 'path' to facilitate the generation of initial and subsequent migration files. While this workaround worked smoothly on some frameworks using Vite (such as React and Svelte), unfortunately, I encountered difficulties with Angular due to the inability to find a suitable method for building the app.
-
In release 5.6.1.1, I decided to retract the proposal for the workaround and refrain from implementing it. As a consequence, migration files will need to be created manually.
Applications/Tutorials
Applications will come on a regular basis
Typical TypeOrm App Directory Structure
In the context of an Author DataSource with three entities
- Author
- Category
- Post
the TypeOrm directory structure could be organized as follows, irrespective of the framework used:

Putting Some Code Into Those Files
DataSource AuthorDataSource.ts
import { DataSource , type DataSourceOptions} from 'typeorm';
import sqliteParams from '../sqliteParams';
import * as entities from '../entities/author';
import * as migrations from '../migrations/author';
const dbName = "YOUR_DATABASE_NAME";
const dataSourceConfig: DataSourceOptions = {
name: 'authorConnection',
type: 'capacitor',
driver: sqliteParams.connection,
database: dbName,
mode: 'no-encryption',
entities: entities,
migrations: migrations, //["../migrations/author/*{.ts,.js}"]
subscribers: [],
logging: [/*'query',*/ 'error','schema'],
synchronize: false, // !!!You will lose all data in database if set to `true`
migrationsRun: false // Required with capacitor type
};
export const dataSourceAuthor = new DataSource(dataSourceConfig);
const authorDataSource = {
dataSource: dataSourceAuthor,
dbName: dbName
};
export default authorDataSource;
Entity author.ts
import {Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn} from 'typeorm';
import {Post} from './post';
@Entity('author')
export class Author {
@PrimaryGeneratedColumn()
id!: number;
@Column()
name!: string;
@Column({nullable: true})
birthday!: string;
@Column({unique: true})
email!: string;
@OneToMany(type => Post, post => post.author)
posts!: Post[];
}
Entity category.ts
import {Entity, PrimaryGeneratedColumn, Column} from 'typeorm';
@Entity('category')
export class Category {
@PrimaryGeneratedColumn()
id!: number;
@Column({unique: true})
name!: string;
}
Entity post.ts
import {Entity, Column, PrimaryGeneratedColumn, ManyToOne, ManyToMany,
JoinTable, type Relation, CreateDateColumn} from 'typeorm';
import {Author} from './author';
import {Category} from './category';
@Entity('post')
export class Post {
@PrimaryGeneratedColumn()
id!: number;
@Column()
title!: string;
@Column('text')
text!: string;
@ManyToMany(type => Category, {
cascade: ['insert']
})
@JoinTable()
categories!: Category[];
@ManyToOne(type => Author, author => author.posts, {
cascade: ['insert']
})
author!: Relation<Author>;
}
Entity index.ts
import { Author } from './author';
import { Category } from './category';
import { Post } from './post';
export { Author, Category, Post };
Migration index.ts
export {};
Capacitor sqliteParams.ts
import { Capacitor } from '@capacitor/core';
import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite';
const sqliteConnection: SQLiteConnection = new SQLiteConnection(CapacitorSQLite);
const sqlitePlugin = CapacitorSQLite;
const platform: string = Capacitor.getPlatform();
const sqliteParams = {
connection: sqliteConnection,
plugin: sqlitePlugin,
platform: platform
}
export default sqliteParams;
Utilities utilities.ts
import { DataSource } from "typeorm";
export const getCountOfElements = (async (connection: DataSource, entity:any): Promise<number> => {
// Get the repository for your entity
const repository = connection.getRepository(entity);
// Use the count() method to query the count of elements in the table
const count = await repository.count();
return count;
});
Correcting a Bug in the TypeOrm Capacitor Driver
-
the bug is referenced "PRAGMA must run under query method in Capacitor sqlite #10687" in the typeorm/issues and it is for
typeorm release > 0.3.18 and < 0.3.24. -
create a
scriptsdirectory at the root of the App. -
create a
modify-typeorm.cjs/modify-typeorm.js (for ESModule)file under this directory with:
const fs = require('fs');
// For ESModule:
// import fs from 'fs';
/* Modify CapacitorQueryRunner.js */
const correctBugInCapacitorQueryRunner = (file) => {
if (fs.existsSync(file)) {
fs.readFile(file, 'utf8', function (err, data) {
if (err) {
return console.error(err);
}
// This key helps to identify that this file has been modified by this script
const isModifiedKey = "/** correctBugInCapacitorQueryRunner */";
const isModifiedIndex = data.indexOf(isModifiedKey);
if (isModifiedIndex !== -1) {
console.warn(`${isModifiedKey} found. Package probably fixed.`);
return;
}
let result = data.replace(
'else if (["INSERT", "UPDATE", "DELETE", "PRAGMA"].indexOf(command) !== -1) {',
'else if (["INSERT", "UPDATE", "DELETE"].indexOf(command) !== -1) {'
);
result += isModifiedKey;
fs.writeFile(file, result, 'utf8', function (err) {
if (err) return console.error(err);
});
});
} else {
utils.warn(`Couldn't find file ${file}`);
}
}
/* Moddify CapacitorDriver.js */
const correctBugInCapacitorDriver = (file) => {
if (fs.existsSync(file)) {
fs.readFile(file, 'utf8', function (err, data) {
if (err) {
return console.error(err);
}
// This key helps to identify that this file has been modified by this script
const isModifiedKey = "/** correctBugInCapacitorDriver */";
const isModifiedIndex = data.indexOf(isModifiedKey);
if (isModifiedIndex !== -1) {
console.warn(`${isModifiedKey} found. Package probably fixed.`);
return;
}
const index = data.indexOf('await connection.run(`PRAGMA foreign_keys = ON`);');
if (index === -1) {
console.warn('Line not found. Package probably fixed.');
return;
}
let result = data.replace(
'await connection.run(`PRAGMA foreign_keys = ON`);',
'await connection.execute(`PRAGMA foreign_keys = ON`, false);'
);
result = result.replace(
'await connection.run(`PRAGMA journal_mode = ${this.options.journalMode}`);',
'await connection.execute(`PRAGMA journal_mode = ${this.options.journalMode}`, false);'
);
result += isModifiedKey;
fs.writeFile(file, result, 'utf8', function (err) {
if (err) return console.error(err);
});
});
} else {
console.log(`Couldn't find file ${file}`);
}
}
correctBugInCapacitorQueryRunner('./node_modules/typeorm/driver/capacitor/CapacitorQueryRunner.js');
correctBugInCapacitorQueryRunner('./node_modules/typeorm/browser/driver/capacitor/CapacitorQueryRunner.js');
correctBugInCapacitorDriver('./node_modules/typeorm/driver/capacitor/CapacitorDriver.js');
correctBugInCapacitorDriver('./node_modules/typeorm/browser/driver/capacitor/CapacitorDriver.js');
- In your
package.jsonadd"postinstall": "node ./scripts/modify-typeorm.cjs"or for ESModule:"postinstall": "npx tsx ./scripts/modify-typeorm.js"
Create the initial migration file
- Under the
migrations/authordirectory create a file1708168009284-InitialAuthorPost.tswhere1708168009284is a timestamp and copy the following code
import { MigrationInterface, QueryRunner } from "typeorm";
export class InitialAuthorPost1708269296396 implements MigrationInterface {
name = 'InitialAuthorPost1708269296396'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "category" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, CONSTRAINT "UQ_23c05c292c439d77b0de816b500" UNIQUE ("name"))`);
await queryRunner.query(`CREATE TABLE "post" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar NOT NULL, "text" text NOT NULL, "authorId" integer)`);
await queryRunner.query(`CREATE TABLE "author" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "birthday" varchar, "email" varchar NOT NULL, CONSTRAINT "UQ_384deada87eb62ab31c5d5afae5" UNIQUE ("email"))`);
await queryRunner.query(`CREATE TABLE "post_categories_category" ("postId" integer NOT NULL, "categoryId" integer NOT NULL, PRIMARY KEY ("postId", "categoryId"))`);
await queryRunner.query(`CREATE INDEX "IDX_93b566d522b73cb8bc46f7405b" ON "post_categories_category" ("postId") `);
await queryRunner.query(`CREATE INDEX "IDX_a5e63f80ca58e7296d5864bd2d" ON "post_categories_category" ("categoryId") `);
await queryRunner.query(`CREATE TABLE "temporary_post" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar NOT NULL, "text" text NOT NULL, "authorId" integer, CONSTRAINT "FK_c6fb082a3114f35d0cc27c518e0" FOREIGN KEY ("authorId") REFERENCES "author" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "temporary_post"("id", "title", "text", "authorId") SELECT "id", "title", "text", "authorId" FROM "post"`);
await queryRunner.query(`DROP TABLE "post"`);
await queryRunner.query(`ALTER TABLE "temporary_post" RENAME TO "post"`);
await queryRunner.query(`DROP INDEX "IDX_93b566d522b73cb8bc46f7405b"`);
await queryRunner.query(`DROP INDEX "IDX_a5e63f80ca58e7296d5864bd2d"`);
await queryRunner.query(`CREATE TABLE "temporary_post_categories_category" ("postId" integer NOT NULL, "categoryId" integer NOT NULL, CONSTRAINT "FK_93b566d522b73cb8bc46f7405bd" FOREIGN KEY ("postId") REFERENCES "post" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_a5e63f80ca58e7296d5864bd2d3" FOREIGN KEY ("categoryId") REFERENCES "category" ("id") ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY ("postId", "categoryId"))`);
await queryRunner.query(`INSERT INTO "temporary_post_categories_category"("postId", "categoryId") SELECT "postId", "categoryId" FROM "post_categories_category"`);
await queryRunner.query(`DROP TABLE "post_categories_category"`);
await queryRunner.query(`ALTER TABLE "temporary_post_categories_category" RENAME TO "post_categories_category"`);
await queryRunner.query(`CREATE INDEX "IDX_93b566d522b73cb8bc46f7405b" ON "post_categories_category" ("postId") `);
await queryRunner.query(`CREATE INDEX "IDX_a5e63f80ca58e7296d5864bd2d" ON "post_categories_category" ("categoryId") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_a5e63f80ca58e7296d5864bd2d"`);
await queryRunner.query(`DROP INDEX "IDX_93b566d522b73cb8bc46f7405b"`);
await queryRunner.query(`ALTER TABLE "post_categories_category" RENAME TO "temporary_post_categories_category"`);
await queryRunner.query(`CREATE TABLE "post_categories_category" ("postId" integer NOT NULL, "categoryId" integer NOT NULL, PRIMARY KEY ("postId", "categoryId"))`);
await queryRunner.query(`INSERT INTO "post_categories_category"("postId", "categoryId") SELECT "postId", "categoryId" FROM "temporary_post_categories_category"`);
await queryRunner.query(`DROP TABLE "temporary_post_categories_category"`);
await queryRunner.query(`CREATE INDEX "IDX_a5e63f80ca58e7296d5864bd2d" ON "post_categories_category" ("categoryId") `);
await queryRunner.query(`CREATE INDEX "IDX_93b566d522b73cb8bc46f7405b" ON "post_categories_category" ("postId") `);
await queryRunner.query(`ALTER TABLE "post" RENAME TO "temporary_post"`);
await queryRunner.query(`CREATE TABLE "post" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar NOT NULL, "text" text NOT NULL, "authorId" integer)`);
await queryRunner.query(`INSERT INTO "post"("id", "title", "text", "authorId") SELECT "id", "title", "text", "authorId" FROM "temporary_post"`);
await queryRunner.query(`DROP TABLE "temporary_post"`);
await queryRunner.query(`DROP INDEX "IDX_a5e63f80ca58e7296d5864bd2d"`);
await queryRunner.query(`DROP INDEX "IDX_93b566d522b73cb8bc46f7405b"`);
await queryRunner.query(`DROP TABLE "post_categories_category"`);
await queryRunner.query(`DROP TABLE "author"`);
await queryRunner.query(`DROP TABLE "post"`);
await queryRunner.query(`DROP TABLE "category"`);
}
}
- Open the
index.tsfile undermigrations/authordirectory and edit it with
import { InitialAuthorPost1708168009284 } from './1708168009284-InitialAuthorPost';
export {InitialAuthorPost1708168009284};
Obviously, you must replace 1708168009284 by the prefix you have in front of -InitialAuthorPost.ts.
Initialize the TypeOrm DataSource
Somewhere in the main.ts file of your App you must initialize your DataSources
...
import { JeepSqlite } from 'jeep-sqlite/dist/components/jeep-sqlite';
import sqliteParams from './databases/sqliteParams';
import authorDataSource from './databases/datasources/AuthorDataSource';
customElements.define('jeep-sqlite', JeepSqlite);
const initializeDataSources = async () => {
//check sqlite connections consistency
await sqliteParams.connection.checkConnectionsConsistency()
.catch((e) => {
console.log(e);
return {};
});
// Loop through the DataSources
for (const mDataSource of [authorDataSource , userDataSource]) {
// initialize
await mDataSource.dataSource.initialize();
if (mDataSource.dataSource.isInitialized) {
// run the migrations
await mDataSource.dataSource.runMigrations();
}
if( sqliteParams.platform === 'web') {
await sqliteParams.connection.saveToStore(mDataSource.dbName);
}
}
}
if (sqliteParams.platform !== "web") {
initializeDataSources();
// Now depending on the Framework render your APP
...
} else {
window.addEventListener('DOMContentLoaded', async () => {
const jeepEl = document.createElement("jeep-sqlite");
document.body.appendChild(jeepEl);
customElements.whenDefined('jeep-sqlite').then(async () => {
await sqliteParams.connection.initWebStore();
await initializeDataSources();
// Now depending on the Framework render your APP
...
})
.catch ((err) => {
console.log(`Error: ${err}`);
throw new Error(`Error: ${err}`)
});
});