Skip to content

Commit

Permalink
feat: scaffold DB migration script for new app projects
Browse files Browse the repository at this point in the history
  • Loading branch information
bajtos committed Nov 30, 2018
1 parent 8156f9d commit f783f07
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 24 deletions.
50 changes: 26 additions & 24 deletions docs/site/Database-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ breakdown of behaviors for automigrate! " %}

LoopBack applications are typically using `RepositoryMixin` to enhance the core
`Application` class with additional repository-related APIs. One of such methods
is `migrateSchema`, which iterates over all registered repositories and asks
them to migrate their schema. Repositories that do not support schema migrations
are silently skipped.
is `migrateSchema`, which iterates over all registered datasources and asks them
to migrate schema of models they are backing. Datasources that do not support
schema migrations are silently skipped.

In the future, we would like to provide finer-grained control of database schema
updates, learn more in the GitHub issue
Expand Down Expand Up @@ -65,33 +65,35 @@ export async function main(options: ApplicationConfig = {}) {
### Auto-update the database explicitly

It's usually better to have more control about the database migration and
trigger the updates explicitly. To do so, you can implement a custom script as
shown below.
trigger the updates explicitly. To do so, projects scaffolded using `lb4 app`
come with a custom CLI script `src/migrate.ts` to run schema migration. Check
out e.g.
[Todo example app](https://github.com/strongloop/loopback-next/blob/master/examples/todo/src/migrate.ts)
to see the full source code of the script.

{% include code-caption.html content="src/migrate.ts" %}
Besides the migration CLI, new projects come with a handy npm script to run the
migration too.

```ts
import {TodoListApplication} from './application';
The migration process consists of two steps now:

export async function migrate(args: string[]) {
const dropExistingTables = args.includes('--rebuild');
console.log('Migrating schemas (%s)', rebuild ? 'rebuild' : 'update');
1. Build the project:

const app = new TodoListApplication();
await app.boot();
await app.migrateSchema({dropExistingTables});
}
```sh
$ npm run build
```

migrate(process.argv).catch(err => {
console.error('Cannot migrate database schema', err);
process.exit(1);
});
```
2. Migrate database schemas (alter existing tables):

```sh
$ npm run migrate
```

Alternatively, you can also tell the migration script to drop any existing
schemas:

After you have compiled your application via `npm run build`, you can update
your database by running `node dist/src/migrate` and rebuild it from scratch by
running `node dist/src/migrate --rebuild`. It is also possible to save this
commands as `npm` scripts in your `package.json` file.
```sh
$ npm run migrate -- --rebuild
```

### Implement additional migration steps

Expand Down
1 change: 1 addition & 0 deletions examples/todo-list/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"test": "lb-mocha \"dist/test/*/**/*.js\"",
"test:dev": "lb-mocha --allow-console-logs dist/test/**/*.js && npm run posttest",
"verify": "npm pack && tar xf loopback-todo-list*.tgz && tree package && npm run clean",
"migrate": "node ./dist/src/migrate",
"prestart": "npm run build",
"start": "node ."
},
Expand Down
25 changes: 25 additions & 0 deletions examples/todo-list/src/migrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: @loopback/example-todo
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {TodoListApplication} from './application';

export async function migrate(args: string[]) {
const existingSchema = args.includes('--rebuild') ? 'drop' : 'alter';
console.log('Migrating schemas (%s existing schema)', existingSchema);

const app = new TodoListApplication();
await app.boot();
await app.migrateSchema({existingSchema});

// Connectors usually keep a pool of opened connections,
// this keeps the process running even after all work is done.
// We need to exit explicitly.
process.exit(0);
}

migrate(process.argv).catch(err => {
console.error('Cannot migrate database schema', err);
process.exit(1);
});
1 change: 1 addition & 0 deletions examples/todo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"test": "lb-mocha \"dist/test/*/**/*.js\"",
"test:dev": "lb-mocha --allow-console-logs dist/test/**/*.js && npm run posttest",
"verify": "npm pack && tar xf loopback-todo*.tgz && tree package && npm run clean",
"migrate": "node ./dist/src/migrate",
"prestart": "npm run build",
"start": "node ."
},
Expand Down
20 changes: 20 additions & 0 deletions packages/cli/generators/app/templates/src/migrate.ts.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {<%= project.applicationName %>} from './application';

export async function migrate(args: string[]) {
const existingSchema = args.includes('--rebuild') ? 'drop' : 'alter';
console.log('Migrating schemas (%s existing schema)', existingSchema);

const app = new <%= project.applicationName %>();
await app.boot();
await app.migrateSchema({existingSchema});

// Connectors usually keep a pool of opened connections,
// this keeps the process running even after all work is done.
// We need to exit explicitly.
process.exit(0);
}

migrate(process.argv).catch(err => {
console.error('Cannot migrate database schema', err);
process.exit(1);
});
1 change: 1 addition & 0 deletions packages/cli/generators/project/templates/package.json.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"test:dev": "lb-mocha --allow-console-logs dist/test/**/*.js",
<% } -%>
<% if (project.projectType === 'application') { -%>
"migrate": "node ./dist/src/migrate",
"prestart": "npm run build",
"start": "node .",
<% } -%>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"test:dev": "mocha dist/test/**/*.js",
<% } -%>
<% if (project.projectType === 'application') { -%>
"migrate": "node ./dist/src/migrate",
"start": "npm run build && node .",
<% } -%>
"prepare": "npm run build"
Expand Down
24 changes: 24 additions & 0 deletions packages/cli/test/integration/generators/app.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

'use strict';

const fs = require('fs');
const {promisify} = require('util');
const path = require('path');
const assert = require('yeoman-assert');
const helpers = require('yeoman-test');
Expand All @@ -13,6 +15,7 @@ const props = {
name: 'my-app',
description: 'My app for LoopBack 4',
};
const {expect} = require('@loopback/testlab');

const tests = require('../lib/project-generator')(
generator,
Expand All @@ -21,6 +24,8 @@ const tests = require('../lib/project-generator')(
);
const baseTests = require('../lib/base-generator')(generator);

const readFile = promisify(fs.readFile);

describe('app-generator extending BaseGenerator', baseTests);
describe('generator-loopback4:app', tests);
describe('app-generator specific files', () => {
Expand Down Expand Up @@ -76,6 +81,25 @@ describe('app-generator specific files', () => {
/export async function setupApplication/,
);
});

it('generates database migration script', () => {
assert.fileContent(
'src/migrate.ts',
/import {MyAppApplication} from '\.\/application'/,
);

assert.fileContent(
'src/migrate.ts',
/const app = new MyAppApplication\(\);/,
);

assert.fileContent('src/migrate.ts', /export async function migrate/);
});

it('creates npm script "migrate-db"', async () => {
const pkg = JSON.parse(await readFile('package.json'));
expect(pkg.scripts).to.have.property('migrate', 'node ./dist/src/migrate');
});
});

describe('app-generator with --applicationName', () => {
Expand Down

0 comments on commit f783f07

Please sign in to comment.