UNPKG

@loopback/docs

Version:

Documentation files rendered at [https://loopback.io](https://loopback.io)

561 lines (430 loc) 16.8 kB
# @loopback/example-lb3-application This example demonstrates how to mount your existing LoopBack 3 (LB3) application on a new LoopBack 4 (LB4) project and how to move the middleware from the LB3 application to a common location so that both the LB3 and LB4 applications can use them. ## Mounting LB3 app on LB4 app 1. Create a new LoopBack 4 project using `lb4 app`. ``` $ lb4 app ``` Fill out the prompts as they fit your project and leave all features enabled. 2. Create a new directory `lb3app` from the root of your LoopBack 4 application and copy your existing LoopBack 3 application there. You should end up with the following directory layout: ``` lb3app/ # LoopBack 3 application in JavaScript common/ models/ # LB3 model files server/ boot/ # LB3 boot scripts public/ # front-end assets (LB4 way) src/ # LoopBack 4 application in TypeScript ``` 3. Move LB3 dependencies to the main package.json file and remove `lb3app/package.json`, `lb3app/node_modules/`, and`lb3app/package-lock.json`, if it exists. Typically you will need to add the following entries, plus any connectors or components you are using in your LB3 application. ```json { "compression": "^1.7.4", "cors": "^2.8.5", "helmet": "^3.16.0", "loopback": "^3.25.1", "loopback-boot": "^3.3.0" } ``` Note: make sure to use `loopback-boot@3.2.1` or higher. Run `npm install` from the root of your LB4 project to install the LB3 dependencies. 4. Disable error handling in your LB3 app, leave it for the new LB4 app. - Remove `lb3app/server/middleware.development.json` - Edit `lb3app/server/middleware.json` and remove the following two entries: - `final` >> `loopback#urlNotFound` - `final:after` >> `strong-error-handler` - Remove `strong-error-handler` from `package.json` dependencies. - In `lb3app/server/config.json`, if `"handleErrors": false` is in `remoting`, move it to `rest`. 5. Move your front-end files from `lb3app/client` to `public/` directory and disable static assets in your LB3 app by removing the following entry in `lb3app/server/middleware.json`: - `files` >> `loopback#static` Also remove `lb3app/server/boot/root.js`, since the main page will be served by the LoopBack 4 project. 6. Remove `lb3app/server/component-config.json` to disable LoopBack 3's explorer. The LoopBack 4 explorer will be used instead. 7. Install and configure `@loopback/booter-lb3app` to boot and mount the LB3 application: 1. `npm install --save @loopback/booter-lb3app` 2. Import the component at the top of your `src/application.ts` file. ```ts import {Lb3AppBooterComponent} from '@loopback/booter-lb3app'; ``` 3. Register the component in Application's constructor: ```ts this.component(Lb3AppBooterComponent); ``` Start the new LB4 application ```sh $ npm start Server is running at http://127.0.0.1:3000 ``` Open the URL printed to console to view your front-end served from `public` directory or go to `http://127.0.0.1:3000/explorer` to explore the REST API. The LB3 application is now successfully mounted on a LB4 application, but we can further optimize the setup so that only the bare neccessary artifacts from the LoopBack 3 application remain. This includes moving almost all of the middleware to a common location so that they are shared by both the LoopBack 3 and the LoopBack 4 apps. ## Migrating Express middleware from LB3 app 1. Update config.json First off, edit the LB3 app's `config.json` file. Remove these properties, as they are not required anymore: ```json "host": "0.0.0.0", "port": 3000, ``` Change `restApiRoot` to `/` or any path where you would like the LB3 app to be mounted; that's relative to the LB4 app's REST API path, which defaults to `/api`. And then add `"handleUnknownPaths": false` to the `rest` property, this will prevent the LB3 REST api from sending a 404 response for requests it cannot handle. The `config.json` file should now look like this: ```json { "restApiRoot": "/", "remoting": { "context": false, "rest": { "handleErrors": false, "handleUnknownPaths": false, "normalizeHttpPath": false, "xml": false }, "json": { "strict": false, "limit": "100kb" }, "urlencoded": { "extended": true, "limit": "100kb" }, "cors": false } } ``` 2. Configure the base Express app We will be using a base Express app (`src/server.ts`) for mounting the LB4 app as described in "[Creating an Express Application with LoopBack REST API](https://loopback.io/doc/en/lb4/express-with-lb4-rest-tutorial.html)" guide. Migrate the LB3 app's middleware from its `middleware.json` file to this Express app, except the one from the `routes` phase (there is a [pending task](https://github.com/loopbackio/loopback-next/issues/4181) to complete the support for this middleware). Each root property in the `middleware.json` object represents a middleware phase, extract the relevant middleware and load them in the Express app in order. An entry like `"compression": {}` translates to `compression()`, and `loopback#favicon` translates to `loopback.favicon()` in TypeScript. For more details about `middleware.json`, refer to [its documentation](https://loopback.io/doc/en/lb3/middleware.json.html). The `middleware.json` file should look like this now: ```js { "routes": { "loopback#rest": { "paths": [ "${restApiRoot}" ] } } } ``` The middleware mounted in the Express app will be shared by both LB3 and LB4 apps. Move any static files from the LB3 app to the `public` directory of the Express app. Move any non-REST routes defined anywhere in the LB3 app to the Express app. This is what the `src/server.ts` file will look like: ```ts import {ApplicationConfig} from '@loopback/core'; import {once} from 'events'; import express, {Request, Response} from 'express'; import * as http from 'http'; import {AddressInfo} from 'net'; import * as path from 'path'; // Replace CoffeeShopApplication with the name of your application import {CoffeeShopApplication} from './application'; const loopback = require('loopback'); const compression = require('compression'); const cors = require('cors'); const helmet = require('helmet'); export class ExpressServer { private app: express.Application; public readonly lbApp: CoffeeShopApplication; public server?: http.Server; public url: String; constructor(options: ApplicationConfig = {}) { this.app = express(); this.lbApp = new CoffeeShopApplication(options); // Middleware migrated from LoopBack 3 this.app.use(loopback.favicon()); this.app.use(compression()); this.app.use(cors()); this.app.use(helmet()); // Mount the LB4 REST API this.app.use('/api', this.lbApp.requestHandler); // Custom Express routes this.app.get('/ping', function (_req: Request, res: Response) { res.send('pong'); }); // Serve static files in the public folder this.app.use(express.static(path.join(__dirname, '../public'))); } public async boot() { await this.lbApp.boot(); } public async start() { await this.lbApp.start(); const port = this.lbApp.restServer.config.port || 3000; const host = this.lbApp.restServer.config.host || '127.0.0.1'; this.server = this.app.listen(port, host); await once(this.server, 'listening'); const add = <AddressInfo>this.server.address(); this.url = `http://${add.address}:${add.port}`; } public async stop() { if (!this.server) return; await this.lbApp.stop(); this.server.close(); await once(this.server, 'close'); this.server = undefined; } } ``` 3. Update `src/index.ts` The Express app will replace the `CoffeeShopApplication` as the entry point for the program, modify the `src/index.ts` file accordingly. ```ts import {ApplicationConfig} from '@loopback/core'; import {ExpressServer} from './server'; export {ApplicationConfig, ExpressServer}; export async function main(options: ApplicationConfig = {}) { const server = new ExpressServer(options); await server.boot(); await server.start(); console.log(`Server is running at ${server.url}`); } ``` 4. Next, modify the application config in `src/index.ts` file to prevent the LB4 app from listening, by adding `listenOnStart: false` in `config.rest` object. The `config` object should now look like this: ```ts const config = { rest: { port: +(process.env.PORT ?? 3000), host: process.env.HOST ?? 'localhost', openApiSpec: { // useful when used with OpenAPI-to-GraphQL to locate your application setServersFromRequest: true, }, listenOnStart: false, }, }; ``` Then, in the `bootOptions` of the `CoffeeShopApplication` class, add the `lb3app` to configure the path of the LB3 APIs. ```js lb3app: { mode: 'fullApp'; } ``` `this.bootOptions` should now look like this: ```ts this.bootOptions = { controllers: { // Customize ControllerBooter Conventions here dirs: ['controllers'], extensions: ['.controller.js'], nested: true, }, lb3app: { mode: 'fullApp', }, }; ``` Start the app: ```sh $ npm start ``` Load [http://localhost:3000/](http://localhost:3000/) on your browser. This will load the Express app, with mounted LB3 and LB4 applications. ## Running LB3 tests from LB4 You can run tests in an LoopBack 3 application from the LoopBack 4 application it mounted on with command `npm test`. We want the LoopBack 3 tests to use the LoopBack 4 server rather than the LoopBack 3 application. The following guide shows how to run - acceptance-level tests making HTTP calls to invoke application logic. e.g. `POST /users/login` - integration-level tests that are using JS API to call application logic. e.g. `MyModel.create()` ### Adding LB3 Test Path in Command In order to run LoopBack 3's tests from their current folder, add LB3 tests' path to `test` entry in package.json: - `"test": "lb-mocha \"dist/**tests**/*_/_.js\" \"lb3app/test/*.js\""` In this case, the test folder is [`/lb3app/test`](https://github.com/loopbackio/loopback-next/tree/spike/lb3test/examples/lb3-application/lb3app/test) from the root of the LoopBack 4 project. This will run LoopBack 4 tests first then LoopBack 3 tests. _To emphasize the setup steps and separate them from the test case details, all the comprehensive test code are extracted into function `runTests`._ ### Running Acceptance Tests First, move any LoopBack 3 test dependencies to `package.json`'s devDependencies and run: ```sh npm install ``` In your test file: 1. When launch the Express server - 1.1 Update to use the Express server when doing requests: ```ts // can use lb4's testlab's supertest as the dependency is already installed const {supertest} = require('@loopback/testlab'); const assert = require('assert'); const should = require('should'); const {ExpressServer} = require('../../dist/server'); let app; function jsonForExpressApp(verb, url) { // use the express server, it mounts LoopBack 3 apis to // base path '/api' return supertest(app.server) [verb]('/api' + url) .set('Content-Type', 'application/json') .set('Accept', 'application/json') .expect('Content-Type', /json/); } ``` - 1.2 Boot and start the Express app in your before hook, and stop the app in the after hook: ```ts describe('LoopBack 3 style tests - Launch Express server', function () { before(async function () { app = new ExpressServer(); await app.boot(); await app.start(); }); after(async () => { await app.stop(); }); // your tests here runTests(); }); ``` 2. When launch the LoopBack 4 application - 2.1 Update to use the LoopBack 4 server when doing requests: ```ts // can use lb4's testlab's supertest as the dependency is already installed const {supertest} = require('@loopback/testlab'); const assert = require('assert'); const should = require('should'); const {CoffeeShopApplication} = require('../../dist/application'); let app; function jsonForLB4(verb, url) { // use the lb4 app's rest server return supertest(app.restServer.url) [verb](url) .set('Content-Type', 'application/json') .set('Accept', 'application/json') .expect('Content-Type', /json/); } ``` - 2.2 Boot and start the LoopBack 4 app in your before hook, and stop the app in the after hook: ```ts describe('LoopBack 3 style tests - launch LB4 app', function () { before(async function () { app = new CoffeeShopApplication(); await app.boot(); await app.start(); }); after(async () => { await app.stop(); }); // your tests here runTests(); }); ``` Example of this use can be seen in [`test/acceptance.js`](https://github.com/loopbackio/loopback-next/tree/master/examples/lb3-application/lb3app/test/acceptance.js) which has the same tests as [`src/__tests__/acceptance/lb3app.acceptance.ts`](https://github.com/loopbackio/loopback-next/blob/spike/lb3test/examples/lb3-application/src/__tests__/acceptance/lb3app.acceptance.ts), but in LB3 style. And [`test/authentication.js`](https://github.com/loopbackio/loopback-next/tree/master/examples/lb3-application/lb3app/test/authentication.js) Now when you run `npm test` your LoopBack 3 tests should be run along with any LoopBack 4 tests you have. Optional: Another option is to migrate your tests to use LoopBack 4 style of testing, similar to `src/__tests__/acceptance/lb3app.acceptance.ts`. Documentation for LoopBack testing can be found in https://loopback.io/doc/en/lb4/Testing-your-application.html. ## Running Integration Tests For the integration tests, LoopBack 3 models were bound to the LoopBack 4 application in order to allow JavaScript API to call application logic such as `Model.create()`. This can be seen in [`packages/booter-lb3app/src/lb3app.booter.ts`](https://github.com/loopbackio/loopback-next/blob/spike/lb3test/packages/booter-lb3app/src/lb3app.booter.ts#L76-L85). In order to retrieve the model from the application's context, `get()` can be used as follows: ```ts describe('LoopBack 3 style integration tests', function () { let app; let CoffeeShop; before(async function () { // If launch the LoopBack 4 application // app = new CoffeeShopApplication(); app = new ExpressServer(); await app.boot(); await app.start(); }); before(() => { // follow the syntax: lb3-models.{ModelName} // If launch the LoopBack 4 application // CoffeeShop = await app.get('lb3-models.CoffeeShop'); CoffeeShop = await app.lbApp.get('lb3-models.CoffeeShop'); }); after(async () => { await app.stop(); }); // your tests here runTests(); }); ``` The syntax for LB3 model's binding key is `lb3-models.{model name}`. Additionally, LB3 datasources are also bound to the LB4 application's context and can be retrieved with a key in the syntax `lb3-datasources.{ds name}`. Example integration tests can be found in [`examples/lb3-application/lb3app/test/integration.js`](https://github.com/loopbackio/loopback-next/tree/master/examples/lb3-application/lb3app/test/integration.js). Example authentication tests can be found in [`examples/lb3-application/lb3app/test/authentication.js`](https://github.com/loopbackio/loopback-next/tree/master/examples/lb3-application/lb3app/test/authentication.js). ## Need help? Check out our [Slack](https://join.slack.com/t/loopbackio/shared_invite/zt-8lbow73r-SKAKz61Vdao~_rGf91pcsw) and ask for help with this tutorial. ## Bugs/Feedback Open an issue in [loopback-next](https://github.com/loopbackio/loopback-next) and we'll take a look. ## Contributions - [Guidelines](https://github.com/loopbackio/loopback-next/blob/master/docs/CONTRIBUTING.md) - [Join the team](https://github.com/loopbackio/loopback-next/issues/110) ## Tests Run `npm test` from the root folder. ## Contributors See [all contributors](https://github.com/loopbackio/loopback-next/graphs/contributors). ## License MIT