loopback4 体验

最近心血来潮看了一遍loopback4的文档,说实话,看的有点吐血,可能是我太菜了,全英的不说,这个还好,没什么奇怪的单词,但东一头西一头的。总体看下来就是两个感觉,第一,看起来敲几行命令代码就出来了,但是你搞不懂那些奇奇怪怪语法糖的话,你没法敲自己的代码,就是不灵活,第二,文档确实没有看到一个完整的例子,都是说到一半突然给你个github链接,让你去看项目。

但是看了这么久,也简单总结下怎么用它去搭建个项目

安装脚手架

1
npm i -g @loopback/cli

如果你用的是

1
yarn global add @loopback/cli

注意要把你yarn的bin配到环境变量里,不然下面一步你执行不了

创建新项目

1
lb4 app

Answer the prompts as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
? Project name: getting-started
? Project description: Getting started tutorial
? Project root directory: (getting-started)
? Application class name: StarterApplication
? Select features to enable in the project:
❯◉ Enable eslint: add a linter with pre-configured lint rules
◉ Enable prettier: install prettier to format code conforming to rules
◉ Enable mocha: install mocha to run tests
◉ Enable loopbackBuild: use @loopback/build helpers (e.g. lb-eslint)
◉ Enable vscode: add VSCode config files
◉ Enable docker: include Dockerfile and .dockerignore
◉ Enable repositories: include repository imports and RepositoryMixin
◉ Enable services: include service-proxy imports and ServiceMixin

然后这个时候,你就可以跑起来自己的项目了。

创建空的Controller

1
lb4 controller
  • Note: If your application is still running, press CTRL+C to stop it before calling the command

  • Answer the prompts as follows:

    1
    2
    3
    4
    5
    6
    ? Controller class name: hello
    ? What kind of controller would you like to generate? Empty Controller
    create src/controllers/hello.controller.ts
    update src/controllers/index.ts

    Controller hello was now created in src/controllers/
  • Paste the following contents into the file /src/controllers/hello.controller.ts:

    1
    2
    3
    4
    5
    6
    7
    8
    import {get} from '@loopback/rest';

    export class HelloController {
    @get('/hello')
    hello(): string {
    return 'Hello world!';
    }
    }
  • Start the application using npm start.

  • Visit http://127.0.0.1:3000/hello to see Hello world!

目前看起来一起都好。

创建CRUD Controller

要创建带有CRUD项目的Controller,首先你就要有model,还是借用官方文档,而且这几步的步骤不能乱

创建model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
lb4 model
? Model class name: todo
? Please select the model base class Entity (A persisted model with an ID)
? Allow additional (free-form) properties? No
Model Todo will be created in src/models/todo.model.ts

Let's add a property to Todo
Enter an empty property name when done

? Enter the property name: id
? Property type: number
? Is id the ID property? Yes
? Is id generated automatically? No
? Is it required?: No
? Default value [leave blank for none]:

Let's add another property to Todo
Enter an empty property name when done

? Enter the property name: title
? Property type: string
? Is it required?: Yes
? Default value [leave blank for none]:

Let's add another property to Todo
Enter an empty property name when done

? Enter the property name: desc
? Property type: string
? Is it required?: No
? Default value [leave blank for none]:

Let's add another property to Todo
Enter an empty property name when done

? Enter the property name: isComplete
? Property type: boolean
? Is it required?: No
? Default value [leave blank for none]:

Let's add another property to Todo
Enter an empty property name when done

? Enter the property name:

create src/models/todo.model.ts
update src/models/index.ts

Model Todo was created in src/models/

创建DataSource

1
2
3
4
5
6
7
8
9
10
lb4 datasource
? Datasource name: db
? Select the connector for db: In-memory db (supported by StrongLoop)
? window.localStorage key to use for persistence (browser only):
? Full path to file for persistence (server only): ./data/db.json

create src/datasources/db.datasource.ts
update src/datasources/index.ts

Datasource Db was created in src/datasources/

创建Repository

1
2
3
4
5
6
7
8
9
lb4 repository
? Please select the datasource DbDatasource
? Select the model(s) you want to generate a repository Todo
? Please select the repository base class DefaultCrudRepository (Juggler bridge)

create src/repositories/todo.repository.ts
update src/repositories/index.ts

Repository TodoRepository was created in src/repositories/

创建Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lb4 controller
? Controller class name: todo
Controller Todo will be created in src/controllers/todo.controller.ts

? What kind of controller would you like to generate? REST Controller with CRUD functions
? What is the name of the model to use with this CRUD repository? Todo
? What is the name of your CRUD repository? TodoRepository
? What is the name of ID property? id
? What is the type of your ID? number
? Is the id omitted when creating a new instance? Yes
? What is the base HTTP path name of the CRUD operations? /todos
create src/controllers/todo.controller.ts
update src/controllers/index.ts

Controller Todo was created in src/controllers/

创建Relation

通过上面的步骤我们再创建一个TodoList 的 model和repositorie

然后我们创建他们两之间的联系

1
2
3
4
5
6
7
8
9
10
$ lb4 relation
? Please select the relation type hasMany
? Please select source model TodoList
? Please select target model Todo
? Foreign key name to define on the target model todoListId
? Source property name for the relation getter (will be the relation name) todos
? Allow TodoList queries to include data from related Todo instances? Yes
create src/controllers/todo-list-todo.controller.ts

Relation HasMany was created in src/

这样我们就给两个model之间创建了联系。

注意,二者必须都有repositorie才可以,不然没法创建关联。

因为创建关联这一步会创建一个Controller出来。

添加JWT校验

1
2
3
$ lb4 example todo
$ cd loopback4-example-todo
$ npm i --save @loopback/authentication @loopback/authentication-jwt

绑定Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
src/application.ts

// ---------- ADD IMPORTS -------------
import {AuthenticationComponent} from '@loopback/authentication';
import {
JWTAuthenticationComponent,
SECURITY_SCHEME_SPEC,
UserServiceBindings,
} from '@loopback/authentication-jwt';
import {DbDataSource} from './datasources';
// ------------------------------------

export class TodoListApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
//...
// ------ ADD SNIPPET AT THE BOTTOM ---------
// Mount authentication system
this.component(AuthenticationComponent);
// Mount jwt component
this.component(JWTAuthenticationComponent);
// Bind datasource
this.dataSource(DbDataSource, UserServiceBindings.DATASOURCE_NAME);
// ------------- END OF SNIPPET -------------
}
}

创建UserController

1
2
3
4
5
6
7
8
9
10
11
$ lb4 controller
? Controller class name: User
Controller User will be created in src/controllers/user.controller.ts

? What kind of controller would you like to generate? Empty Controller

create src/controllers/user.controller.ts
update src/controllers/index.ts

Controller User was created in src/controllers/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/src/controllers/user.controller.ts

// ---------- ADD IMPORTS -------------
import {inject} from '@loopback/core';
import {
TokenServiceBindings,
MyUserService,
UserServiceBindings,
UserRepository,
} from '@loopback/authentication-jwt';
import {TokenService} from '@loopback/authentication';
import {SecurityBindings, UserProfile} from '@loopback/security';
import {repository} from '@loopback/repository';
// ----------------------------------

constructor(
@inject(TokenServiceBindings.TOKEN_SERVICE)
public jwtService: TokenService,
@inject(UserServiceBindings.USER_SERVICE)
public userService: MyUserService,
@inject(SecurityBindings.USER, {optional: true})
public user: UserProfile,
@repository(UserRepository) protected userRepository: UserRepository,
) {}

添加JWT校验

1
2
3
4
5
6
7
// ---------- ADD IMPORTS -------------
import {authenticate} from '@loopback/authentication';
// ------------------------------------
@authenticate('jwt') // <---- Apply the @authenticate decorator at the class level
export class TodoController {
//...
}

问题

到目前为止,你已经可以通过loopback提供的api来进行todo的CRUD了,看起来很happy是不是

但是有几个问题我在文档没有找到答案

  • 我没找到可以通过一个请求更新两个model的地方,比如上面的TodoList和Todo,我想要更新这两个相关的model,我必须发两个请求才可以。

  • 自定义Request body非常麻烦,你必须严格符合它的规范。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const requestBodyObject = {
    description: 'data',
    content: {
    'application/x-www-form-urlencoded': {
    schema: {
    type: 'object',
    properties: {
    name: {type: 'string'},
    location: {
    type: 'object',
    properties: {
    lat: {type: 'number'},
    lng: {type: 'number'},
    },
    },
    tags: {
    type: 'array',
    items: {type: 'string'},
    },
    },
    },
    },
    },
    };
  • Model中我定义一个变量的类型是我自定义的一个enum类型,项目没法启动,错误原因是无法解析我自定义的类型。

  • 用它的JWT校验,可以不用自定义UserModel,虽然省事,但是我也没找到可以让我自定义User的地方,那我就没法给User加我需要的字段了。

  • 鉴权用了一个叫casbin的包,文档也是全英的,看不太懂。

以上就是我遇到的一些问题,希望随着我对它理解的加深,可以解决吧。

2021.03.20更新(填上期的坑)

如何自定义RequestBody

目前发现最快捷的方法就是通过 lb4 model,创建一个Model,格式就是你需要的RequestBody,当然你也可以自己完全手打一个,比如:

1
2
3
4
5
6
7
8
@model()
export class NewUserRequest extends User {
@property({
type: 'string',
required: true,
})
password: string;
}

然后在请求上

1
2
3
4
5
6
7
8
9
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(NewUserRequest, {
title: 'NewUser',
}),
},
},
})

JWT校验如何用自己的Model

在application.ts中改变User,UserCredentials,RefreshToken,的Model,Repository,Service的绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//application.ts


// Mount authentication system
this.component(AuthenticationComponent);
// Mount jwt component
this.component(JWTAuthenticationComponent);
// Bind datasource
this.dataSource(MemoryDataSource, UserServiceBindings.DATASOURCE_NAME);

this.bind(UserServiceBindings.USER_REPOSITORY).toClass(UserRepository);
this.bind(UserServiceBindings.USER_CREDENTIALS_REPOSITORY).toClass(
UserCredentialsRepository,
);
this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService);


this.bind(TokenServiceBindings.TOKEN_SECRET).to('my-secret');
this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService);

this.bind(RefreshTokenServiceBindings.REFRESH_REPOSITORY).toClass(RefreshTokenRepository);
this.bind(RefreshTokenServiceBindings.REFRESH_TOKEN_SERVICE).toClass(RefreshtokenService);
this.bind(RefreshTokenServiceBindings.REFRESH_SECRET).to(
'my-refresh-secret',
);

this.bind(TokenServiceBindings.TOKEN_EXPIRES_IN).to((60 * 60).toString());
this.bind(RefreshTokenServiceBindings.REFRESH_EXPIRES_IN).to(
(7 * 24 * 60 * 60).toString(),
);