gpt4 book ai didi

typescript - 为 NestJs REST API 创建 DTO、BO 和 DAO

转载 作者:行者123 更新时间:2023-12-04 18:55:12 29 4
gpt4 key购买 nike

我想开始使用 NestJs 创建 REST API,但我不确定如何设置可扩展层通信对象。

所以从文档中了解如何 get started我想出了一个 UsersController处理 HTTP 请求和响应,一个 UsersService处理 Controller 和数据库访问器之间的逻辑以及UsersRepository它负责数据库管理。

我使用 TypeORM package由 NestJs 提供,所以我的数据库模型是

@Entity('User')
export class UserEntity extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ unique: true })
username: string;

@Column()
passwordHash: string;

@Column()
passwordSalt: string;
}

但是您可能知道这个模型必须映射到其他模型,反之亦然,因为您不想将密码信息发送回客户端。我将尝试用一个简单的例子来描述我的 API 流程:

Controller

首先,我有一个 GET /users/:id 的 Controller 端点和 POST /users .
  @Get(':id')
findById(@Param() findByIdParamsDTO: FindByIdParamsDTO): Promise<UserDTO> {
// find user by id and return it
}

@Post()
create(@Body() createUserBodyDTO: CreateUserBodyDTO): Promise<UserDTO> {
// create a new user and return it
}

我设置了 DTOs并想先验证请求。我使用 class-validator NestJs 提供的包并创建了一个名为 的文件夹请求 DTO .通过 id 查找某些内容或通过 url 参数通过 id 删除某些内容是可重用的,因此我可以将其放入共享文件夹中以用于其他资源,如组、文档等。
export class IdParamsDTO {
@IsUUID()
id: string;
}

POST 请求是用户特定的
export class CreateUserBodyDTO {
@IsString()
@IsNotEmpty()
username: string;

@IsString()
@IsNotEmpty()
password: string;
}

现在 Controller 输入在执行业务逻辑之前得到验证。对于回复,我创建了一个名为 的文件夹响应 DTO 但目前它只包含没有密码信息的数据库用户
export interface UserDTO {
id: string;
username: string;
}

服务

该服务需要来自参数和正文的捆绑信息。
  public async findById(findByIdBO: FindByIdBO): Promise<UserBO> {
// ...
}

public async create(createBO: CreateBO): Promise<UserBO> {
// ...
}

GET 请求只需要 ID,但创建 BO 可能会更好。因为您可能希望稍后从字符串 ID 切换到整数。 “按 id 查找”BO 是可重用的,我将其移至共享目录
export interface IdBO {
id: string;
}

对于用户创建,我创建了文件夹 请求BOs
export interface CreateBO {
username: string;
password: string;
}

现在为 回应BOs 结果是
export interface UserBO {
id: string;
username: string;
}

你会注意到这和 是一样的。用户DTO .所以其中一个似乎是多余的?

存储库

最后我设置了 DAOs对于存储库。我可以使用自动生成的用户存储库并处理我上面提到的数据库模型。但是我必须在我的服务业务逻辑中处理它。创建用户时,我必须在服务中进行,并且只调用 usermodel.save来自存储库的功能。

否则我可以创建 请求DAO

共享的一个..
export interface IdDAO {
id: string;
}

还有 POST DAO
export interface CreateDAO {
username: string;
password: string;
}

有了它,我可以在我的存储库中创建一个数据库用户,并使用 映射数据库响应。响应DAO 但这始终是没有密码信息的整个数据库用户。似乎又产生了很大的开销。

我想知道我使用 3 个请求和 3 个响应接口(interface)的方法是否太多并且可以简化。但我想保留一个灵活的层,因为我认为这些层应该是高度独立的……另一方面,那里会有大量的模型。

提前致谢!

最佳答案

我通过使用 class-transformer 来表示用户(内部和外部)的单个类来处理这个问题。库(由 NestJs 推荐)来处理暴露用户和内部用户之间的差异,而无需定义两个类。
这是使用您的用户模型的示例:
定义用户类
由于这个用户类被保存到数据库中,我通常为每个数据库对象期望拥有的所有字段创建一个基类。比方说:

export class BaseDBObject {
// this will expose the _id field as a string
// and will change the attribute name to `id`
@Expose({ name: 'id' })
@Transform(value => value && value.toString())
@IsOptional()
// tslint:disable-next-line: variable-name
_id: any;

@Exclude()
@IsOptional()
// tslint:disable-next-line: variable-name
_v: any;

toJSON() {
return classToPlain(this);
}

toString() {
return JSON.stringify(this.toJSON());
}
}
接下来,我们的用户将扩展这个基本类:
@Exclude()
export class User extends BaseDBObject {
@Expose()
username: string;

password: string;

constructor(partial: Partial<User> = {}) {
super();
Object.assign(this, partial);
}
}
我在这里使用了一些来自 class-transformer 的装饰器。当我们在服务器外部公开类时,库来更改这个内部用户(所有数据库字段都完好无损)。
  • @Expose - 如果 class-default 是排除
  • ,将公开属性
  • @Exclude - 如果 class-default 要公开
  • ,将排除该属性
  • @Transform - 'exporting' 时更改属性名称

  • 这意味着在运行 classToPlain 之后函数来自 class-transformer ,我们在给定类上定义的所有规则都将被应用。
    Controller NestJs添加装饰器以确保从 Controller 端点返回的类将使用 classToPlain转换对象的函数,返回结果对象,省略所有私有(private)字段和转换(如将 _id 更改为 id )
    @Get(':id')
    @UseInterceptors(ClassSerializerInterceptor)
    async findById(@Param('id') id: string): Promise<User> {
    return await this.usersService.find(id);
    }

    @Post()
    @UseInterceptors(ClassSerializerInterceptor)
    async create(@Body() createUserBody: CreateUserBodyDTO): Promise<User> {
    // create a new user from the createUserDto
    const userToCreate = new User(createUserBody);

    return await this.usersService.create(userToCreate);
    }
    服务
    @Injectable()
    export class UsersService {
    constructor(@InjectModel('User') private readonly userModel: Model<IUser>) { }

    async create(createCatDto: User): Promise<User> {
    const userToCreate = new User(createCatDto);
    const createdUser = await this.userModel.create(userToCreate);

    if (createdUser) {
    return new User(createdUser.toJSON());
    }
    }

    async findAll(): Promise<User[]> {
    const allUsers = await this.userModel.find().exec();
    return allUsers.map((user) => new User(user.toJSON()));
    }

    async find(_id: string): Promise<User> {
    const foundUser = await this.userModel.findOne({ _id }).exec();
    if (foundUser) {
    return new User(foundUser.toJSON());
    }
    }
    }
    因为在内部我们总是使用 User 类,所以我将从数据库返回的数据转换为 User 类实例。
    我正在使用 @nestjs/mongoose ,但基本上在从数据库中检索用户之后, mongoose 的一切都是相同的和 TypeORM .
    注意事项
    @nestjs/mongoose ,我无法避免创建 IUser传递给mongo的接口(interface) Model类,因为它期望扩展 mongodb Document 的东西
    export interface IUser extends mongoose.Document {
    username: string;

    password: string;
    }
    获取用户时,API 将返回转换后的 JSON:
    {
    "id": "5e1452f93794e82db588898e",
    "username": "username"
    }
    Here's the code for this example in a GitHub repository .
    更新
    如果您想查看使用 typegoose 的示例要消除接口(interface)(基于 this blog post ),请查看 here for a model , 和 here for the base model

    关于typescript - 为 NestJs REST API 创建 DTO、BO 和 DAO,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59547243/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com