Reputation: 4882
I am writing a React-Native app which heavily relies on local storage, and have chosen TypeORMto handle this.
I am now entering a phase where my project is live, and I want to add new functionalities that require an adjustment to my datamodel. To start, I have used the react-native-example project. I have built my datamodel on this and would now like to implement a migration into it.
There seems to be no definitive manual/guide on how to perform migrations on React-Native. Since I don't have access to the databases of my users, I can't run the migrations manually. So far, I found out that there is a 'migrationsRun' option in the connection settings which can be set to true, forcing migrations to be executed at every launch of the app, if needed.
Question then is: How should the actual migrations be created, what should they look like (plain sql queries?), and where should I put them?
Any guidance on migrations in a react-native app would be very helpful.
Upvotes: 7
Views: 3398
Reputation: 123
I had the same problem and managed to get migrations running in a react native context by providing the following properties as the createConnection
options:
false
true
migrationFile
]Where the migrationFile
is directly imported (rather than using a file path regex) and looks like the below (with inline SQL statements):
import { MigrationInterface } from 'typeorm'
module.exports = class PostRefactoring1579804977343 implements MigrationInterface {
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "workouts" ADD COLUMN "questionSetId" text`)
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "workouts" DROP COLUMN "questionSetId"`)
}
}
The above migration file looks slightly different to the examples you will see on https://typeorm.io/#/migrations as I'm not using TypeScript plus I had to add module.exports
as I'm importing the file directly
The major (almost) undocumented discovery for seemingly getting this to work in react-native is the magical migrationsRun
property which I discovered here reactnativeconnectionoptions
You can add more migration files over time to the migrations
array config as needed and be sure to give them a unique class name with appropriate timestamp. It seems to keep an on device log of which migrations have run - to stop them running multiple times on the same device
Note: the requirement to have synchronize: false
means that you will need to do an initial synchronize of your schemas upon first install of the app (which I initially set using a flag in AsyncStorage to indicate first app install)
ALTERNATIVE APPROACH
The above approach means we are then manually maintaining our schema setup after the initial user installation. Whereas I quite like the idea of having the schema managed automatically if possible - with migrations used just to manipulate the data.
So an alternative way to achieve this is to call the synchronize
and runMigrations
methods directly when initialising the connection. In this instance you will need to check that particular tables exist within your migration scripts as they will run first, and so the tables they need to update may not yet exist if it's the first time the user opens the app after installation
Connection options:
export const initialiseDataStoreService = async () => {
const connection = await createConnection({
type: 'react-native',
database: 'main.sqlite',
location: 'default',
logging: [ 'error', 'schema'],
entities: [
CoreData,
Workouts,
],
migrations: [PostRefactoring1579804977343],
})
await connection.runMigrations()
await connection.synchronize()
return connection
}
Updated migration script:
import { MigrationInterface, TableColumn } from 'typeorm'
module.exports = class PostRefactoring1579804977343 implements MigrationInterface {
async up(queryRunner) {
const hasWorkoutsTable = await queryRunner.hasTable('workouts')
if (hasWorkoutsTable) {
await queryRunner.addColumn(
'workouts',
new TableColumn({
name: 'questionSetId',
type: 'text',
// we make the new field nullable in order to enable the update
// for existing data (schema sync will later update this column to be non
// nullable)
isNullable: true,
}),
)
await queryRunner.query(
`UPDATE workouts SET questionSetId = "MIGRATION-PLACEHOLDER" WHERE questionSetId IS NULL`,
)
}
}
async down(queryRunner) {
const hasWorkoutsTable = await queryRunner.hasTable('workouts')
if (hasWorkoutsTable) {
await queryRunner.dropColumn('workouts', 'questionSetId')
}
}
}
Upvotes: 9