精炼和巢。JS样板
#javascript #网络开发人员 #nestjs #refine

我们正在寻找一种解决方案,该解决方案弥合了BAAS平台(例如AppWrite或Supabase)之间的差距,并从头开始构建。我们需要一个系统,该系统可以灵活地实施BI,数据管理,物联网,自动化和类似应用程序,同时还结合了身份验证,RBAC,社交登录,配备CRUD操作,测试等的前端等功能。

这就是完善和嵌套样板的诞生方式。

来源:

精炼样板:https://github.com/poliath/poliath-refine-boilerplate
Nest.js样板:https://github.com/poliath/nestjs-poliath-boilerplate
快速启动指南:https://github.com/poliath/nestjs-poliath-boilerplate/blob/master/QUICK_START_GUIDE.md

这是快速入门指南

精炼 +巢。JS样板

上次编辑:Ivan Golubic
上次编辑时间:2023年8月16日下午3:27

本文档描述了使用Poliath Refine和Nest.js模板创建新项目的过程。

要求

node.js:最低版本为16,但强烈建议使用18版

npm:安装和更新

Docker:安装,配置和运行

IDE:建议使用WebStorm,尽管VS代码将成为

后端

为了启动我们的nest.js后端,我们必须基于模板创建我们的新项目

git clone --depth 1 https://github.com/poliath/nestjs-poliath-boilerplate.git my-app
cd my-app/
cp env-example .env

快速运行

为了启动项目而无需开发(用于快速测试),从您的应用程序目录运行命令:

docker compose up -d

此命令将:

  1. 等待Postgres数据库容器旋转
  2. 运行迁移(在数据库中创建所需的表)
  3. 运行播种机(创建2个用户和少数随机文章)
  4. 在生产模式下运行应用

前端

为了查看您的后端服务数据,我们必须运行我们的前端应用程序(基于完善的React应用程序)。克隆poliath-refine-boilerplate存储库中的首选目录:

git clone --depth 1 https://github.com/poliath/poliath-refine-boilerplate.git frontend

现在,前往您的目录并安装依赖项:

cd frontend/
npm install

安装依赖项后,我们必须构建我们的项目并运行预览模式(基本上是一个可以为我们的静态内容服务的本地服务器。

npm run build
npm run preview

此命令将向您显示您的应用程序正在运行的位置,例如:

Local:   http://localhost:4173/

前往位置并登录。您应该查看演示用户和文章。

RBAC解释了

后端和前端都已经实施了RBAC。管理员用户可以对用户对象进行所有操作,而用户用户只能查看文章并管理文章。在本文档的后面

  1. admin@example.com - Admin
  2. john.doe@example.com - User

**************************************************************************************************************************************************** ****** 两个用户的密码是: ********************** ***************************秘密

推断者解释了

refine.dev的重要功能之一是推论器(简而言之,它基于数据库中的数据生成CRUD仪表板,并为您提供代码)。如果您单击*************** 文章 ************************会看到弹出窗口。此弹出窗口在******* 用户 *******上都不可见样板虽然在文章中不是为了展示目的。您可以在此处阅读有关推断器的更多信息:https://refine.dev/docs/packages/documentation/inferencer/


如果一切正常,那么我们可以继续开发设置并开发我们的应用程序。

后端 - 发展

***** 如果快速运行的Docker容器仍在运行,请关闭它们,以避免在开发阶段发生任何碰撞! *****前往您的后端目录(例如my-app)并运行:

docker compose down

**************************************************************************************************************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ** 如果前端正在运行它也可能会引起一些混乱,那么通过按下(ctrl+c或适合您的操作系统的适当快捷方式)的简单退出预览服务器。 *** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** *********************************************************

在此步骤中,我们将假设您已经克隆了nestjs-poliath-boilerplate项目。在IDE中打开您的后端目录。

为了开始开发,我们需要更新两个环境变量(因为我们将使用NPM在本地计算机上运行Nest.js项目,但是我们的数据库和某些开发工具将在Docker中。打开您的.env文件和;

    hakácaomcade2éâét2
  1. MAIL_HOST=maildevel更改为country5

运行Docker容器:

docker compose up -d postgres adminer maildev

请注意,此命令不会启动我们的api容器。

在本地运行Nest.js项目

由于我们正在本地机器上运行项目,因此我们必须安装依赖项,运行播种机并在开发环境中开始。

npm install

npm run migration:run

npm run seed:run

npm run start:dev

这将启动我们的后端,您应该在终端中看到日志。

前端 - 开发

在开发模式下运行前端非常简单。打开IDE中的前端目录。在终端中,运行以下命令:

npm run dev

这将启动开发服务器,它将向您显示其当前正在运行的端口。如果一切都很好,您应该能够登录并查看您的应用程序。

后端 - 添加新资源

现在,我们将为后端添加新资源。为此,它将是一个简单的任务,它将具有:ID,标题,完成,创建,在作者,受让人。

我们将在大多数工作中使用Nest CLI,所以让我们安装它:

npm install -g @nestjs/cli

NEST提供CRUD GENETARE作为其CLI工具的一部分。它为您的资源生成了起点。让我们生成我们的任务资源:

nest g resource

这将启动一个简单的向导:

? What name would you like to use for this resource (plural, e.g., "users")? tasks

接下来,选择“转运层的REST API”。

选择是生成Crud入口点的是

**************************************** 这将为我们的任务生成所有需要的文件在任务目录下的资源。 NEST不会默认创建DTO,它将仅创建一个我们应该在数据中填充的简单类。 ************************************************************************* *********

注意:不是为每个模型创建数据库表,而是针对那些被称为实体的模型。要将模型声明为实体,我们只需要添加 @entity()''在定义我们的模型的类声明之前的装饰器。

import {
  Column,
  CreateDateColumn,
  Entity,
  ManyToOne,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';
import { User } from '../../users/entities/user.entity';

@Entity()
export class Task {
  @PrimaryGeneratedColumn()
  id: number;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt?: Date;

  @Column({ type: String, nullable: false })
  title: string;

  @Column({ default: false })
  done: boolean;

  @ManyToOne(() => User, (user) => user.tasks)
  author: User;

  @ManyToOne(() => User, (user) => user.tasks)
  assignee: User;
}

在上述代码中,我们创建了我们的任务模型。 Nest提供了多个装饰器,例如 * @CreateDateColumn() *,它们将在创建实体时自动添加日期。 Additionally *********************************PrimaryGeneratedColumn* *****************************************。我们可以通过各种********* *********诸如类型或默认值之类的选项。

此外,我们有************************************************************************************************************** **关系,因为用户可以是许多任务的作者,并且用户可以拥有多个任务,在我们的情况下,在我们的情况下,任务只有一个受让人。

此外,由于我们有用于用户的外国关系,因此我们必须将其添加到************** user.entity.ts: < /strong> *************

@OneToMany(() => Task, (task) => task.author)
  tasks?: Task[];

@OneToMany(() => Task, (task) => task.assignee)
assignedTasks?: Task[];

接下来,我们必须创建一个迁移才能将我们的更改应用于数据库表。

npm run migration:generate -- src/database/migrations/CreateTasks

此命令将在迁移下生成新的迁移文件。名称将为

接下来,我们必须运行迁移:

npm run migration:run

如果我们以前的迁移有问题,我们可以简单地将其恢复:

npm run migration:revert

在我们的任务控制器类上方,我们必须添加以下内容:

@Controller({
  path: 'tasks',
  version: '1',
})
@UseGuards(AuthGuard('jwt'))
@ApiTags('Tasks')
@ApiBearerAuth()

控制器装饰器将定义该类充当控制器,这是任务路径的调用,该API的版本为1。********************************************************** ************************************************ 这也将被可见**************************。

使用Guards Decorator将定义警卫,在这种情况下只有JWT(因此,只有身份验证的用户才能访问此路线。此样板还支持******************* < strong> rolesguard ***************** - 与******* 角色 *******装饰器,以限制特定用户组的用户访问。

apitags定义了如何以摇摇晃力的方式调用此路线集。

apibearerauth定义该控制器需要携带者令牌才能访问。

控制器中的方法

由于我们选择了在资源创建期间生成CRUD入口点的是,因此每个方法都在控制器中定义,并具有自己的装饰器(帖子,get,delete,patch)。

致电********************* findall() ************ * 方法。在这种情况下,我们可以使用Postman致电koude8,它应该返回消息:此操作返回所有任务,因为我们的********************** ******* 尚未实施服务! ************************************************************ ****

如果它返回错误401,则意味着您没有提供携带者令牌,您将通过以下JSON主体致电koude9的POST请求来获得。

{
    "email": "admin@example.com",
    "password" : "secret"
}

然后将您的携带者令牌添加到Postman的授权字段中。

如果要限制特定(或多个)角色的特定途径,则必须在该方法上方添加以下装饰器:

@Roles(RoleEnum.user)
@UseGuards(AuthGuard('jwt'), RolesGuard)

这将仅限于登录用户的用户的访问。而且,如果您尝试调用获取方法到上一个路线(使用管理员帐户登录时),您将获得以下错误:

{
    "message": "Forbidden resource",
    "error": "Forbidden",
    "statusCode": 403
}

dto

数据传输对象基本上将我们的数据转换在我们的服务和数据库之间。在Typeorm中,它可以定义我们实体的结构和验证,在这种情况下是任务。

默认情况下,创建和更新会生成两个主要的DTO ,在某些情况下会有所不同。在我们的情况下,它将扩展我们的创建:dto:

export class UpdateTaskDto extends PartialType(CreateTaskDto) {}

您可以看到这是默认情况下已经生成的。

回到板条箱。

根据我们的任务实体,我们必须提供标题,作者(ID)和受让人(ID)。

为简单起见,我们的任务DTO看起来像这样:

import { IsNotEmpty } from 'class-validator';

export class CreateTaskDto {
  @IsNotEmpty()
  title: string;
  @IsNotEmpty()
  author: number;
  @IsNotEmpty()
  assignee: number;
}

基本上仅定义其中一个字段不能为空。有多个可以使用的装饰器和验证器。

服务

为了实际使用我们的数据做点事,我们必须创建一些逻辑。逻辑在tasks.service.ts文件中处理。首先,我们将编写一个用于创建新任务的代码。

at。一开始,我们必须将存储库注入我们服务类的构造函数:

constructor(
    @InjectRepository(Task)
    private tasksRepository: Repository<Task>,
  ) {}

存储库是Typeorm的一部分,它基本上用于我们与数据库的交互。

为了插入我们的任务作者,我们必须将当前用户传递给服务,因此我们的创建方法看起来像这样:

create(createTaskDto: CreateTaskDto, user: User) {
    createTaskDto.author = user;
    const newTask = this.tasksRepository.save(
      this.tasksRepository.create(createTaskDto),
    );

    return newTask;
  }

**************************************************************************************************************************************************** ****************************************************** ****************************************************** ** ,但这无法开箱即用,因为我们需要将用户对象传递给我们的创建方法。此样板包含当前返回用户的电流装饰器。 **************************************************************************************** ****************************************************** ****************************************************** ******************

所以,编辑任务。controller.ts:

@Post()
  create(@Body() createTaskDto: CreateTaskDto, @CurrentUser() user: User) {
    return this.tasksService.create(createTaskDto, user);
  }

现在,我们可以使用以下JSON主体将发布请求发送到koude8路线:

{
    "title": "This is a task 4",
    "assignee": 2
}

这将把此任务分配给我们的用户ID = 2(John Doe),作者将是我们在用户中签名(admin)。

**************************************************************************************************************************************************** ****************************************************** **** 接下来,我们将创建用于获取所有任务的服务。 ***************************************************************** ****************************************************** **********************************

为了做到这一点,我们必须编辑***** findall() >

findAll() {
    return this.tasksRepository.find();
  }

这是一种简单的方法,可以调用********* find() *********,基本上是喜欢的SELECT * FROM TASKS;现在我们可以在koude8端点上调用获取方法。

这将返回我们任务的JSON数组:

[
    {
        "id": 1,
        "createdAt": "2023-08-15T10:18:14.437Z",
        "updatedAt": "2023-08-15T10:18:14.437Z",
        "title": "This is a task 1",
        "done": false
    },
    {
        "id": 2,
        "createdAt": "2023-08-15T16:45:51.630Z",
        "updatedAt": "2023-08-15T16:45:51.630Z",
        "title": "This is a task 2",
        "done": false
    }
]

接下来,我们将实施************* findOne() ************** *为了获取特定任务数据的方法:

@Get(':id')
  findOne(@Param('id') id: string) {
    return this.tasksService.findOne({ id: +id });
  }

首先,我们将在控制器中更新我们的FindOne方法以通过{id: +id},因为我们将使用************ entityCondition ******* ******通过字段。最后,我们的服务中的发现方法将看起来像这样:

findOne(fields: EntityCondition<Task>): Promise<NullableType<Task>> {
    return this.tasksRepository.findOne({ where: fields });
  }

******************************************************** 接下来我们将写我们服务中的更新方法: ******************************************** *

update(id: Task['id'], payload: DeepPartial<Task>): Promise<Task> {
    return this.tasksRepository.save(
      this.tasksRepository.create({
        id,
        ...payload,
      }),
    );
  }

您可以看到,我们正在接受任务ID和有效负载,这基本上是部分DTO,此方法返回我们更新的任务。

*************** 接下来,我们将实施删除服务 ***************************************************

这是非常简单的方法:

remove(id: number) {
    return this.articlesRepository.delete(id);
  }

请注意,这些服务有各种各样的实现,我提供了简单的服务,有多种更新和处理错误,响应等的方法。但是可以根据特定的用例来实现。

前端 - 添加新资源

一旦我们启动并运行了后端,我们就可以实现前端。

我们将使用************* 完善Cli *************新资源。使用Refine Create Command创建项目时,将安装完善的CLI,但在此样板中已经可用。

将终端定位到前端目录(例如my-app)并运行以下命令:

npm run refine create-resource

此向导会要求您定义您的资源名称(在我们的情况下为任务),并随身携带所有页面。这将生成******* 任务 *******目录,并带有所需的所有页面。另外,它将使用新的资源数据更新app.tsx。

请注意,此命令不会生成路由,并且仅在生成页面中使用推论器。

**************************************************************************************************************************************************** ************************** 首先,我们将为我们的任务添加路线 **** ****************************************************** ****************

<Route path="/tasks">
                  <Route index element={<TasksList />} />
                  <Route path="create" element={<TasksCreate />} />
                  <Route path="edit/:id" element={<TasksEdit />} />
                  <Route path="show/:id" element={<TasksShow />} />
                </Route>

这些路线与我们的页面直接相关,但是由于RBAC限制,它们将立即看到。

我们必须编辑********* casbin *********访问控制。打开src/casbin/accessControl.ts并将以下内容添加到适配器:

p, 1, tasks, (list)|(create)|(edit)|(show)|(delete)
p, 2, tasks, (list)|(create)|(edit)|(show)|(delete)

这将使用户和管理员都能进行所有CRUD操作。

现在,如果您在Localhost上刷新前端,然后单击任务,则应查看任务列表。

**************************************************************************************************************************************************** **** 如果未自动添加,请在app.tsx中的任务资源添加以下内容: ********************************************************************************* ******************************************

meta: {
           canDelete: true,
      },

所有页面都是使用推论器生成的,一个来自Refine.dev的零件:https://refine.dev/docs/packages/documentation/inferencer

如果您单击“推论器”弹出,您将看到有关任务列表的生成代码,复制此代码并将其粘贴到tasks/list.tsx(替换当前代码)中。弹出将消失,您将具有自定义页面的灵活性。

如果您的控制台中存在错误或您的页面空白,则页面导出可能是错误的(任务清单而不是Taskslist),请检查此类问题。

I18N-翻译

导出后,我们可以为页面编辑翻译以显示真实值而不是对象字段。打开public/locales/en/common.json

将以下对象添加到与用户相同的级别:

"tasks": {
    "tasks": "Tasks",
    "fields": {
      "id": "ID",
      "title": "Title",
      "done": "Done",
      "createdAt": "Created at",
      "updatedAt": "Updated at"
    },
    "titles": {
      "create": "Create task",
      "edit": "Edit task",
      "list": "Tasks",
      "show": "Show task"
    }
  },

此外,在DocumentTitle下,与用户相同的级别添加以下级别:

"tasks": {
      "list": "Tasks | Poliath Manager",
      "show": "#{{id}} Show task | Poliath Manager",
      "edit": "#{{id}} Edit task | Poliath Manager",
      "create": "Create new task | Poliath Manager",
      "clone": "#{{id}} Clone task | Poliath Manager"
    },

上面用于页面标题。

为其他语言添加相同的字段。

当您刷新页面时,您应该看到字段现在的名称。

通过我们的前端创建新任务

这很简单,只需单击创建按钮并填写字段即可。 * 当然,您可以将推论器代码 *复制到您的create.tsx文件,例如在字段上删除并更新了,因为这些文件已在我们的后端。

单击“保存” - 您将获得错误422-无法处理的实体,这是因为我们没有传递受让人参数。但是如何获得呢?

我们可以使用************ Uselist ***********************是一个粗略的选择,因为它将所有用户加载到内存中,并且我们正在客户端进行解析。相反,我们应该使用USEMANY,它将调用我们的后端方法并仅检索我们实际需要的过滤用户。

const { data: data, isLoading } = useList<IUser>({
        resource: 'users'
    });

挥舞着

精炼提供了一些解决方案,例如数据提供商,但有时该代码不满足我们的需求。这就是为什么存在Swizzle的原因,它基本上会根据现有的预定义代码生成可以自定义的代码。

npm run refine swizzle

这就是我们可以编辑组件,验证页面,验证提供商等的方式

https://refine.dev/docs/tutorial/understanding-dataprovider/swizzle/#what-is-swizzle

发送电子邮件

此样板已实施了电子邮件服务,并且在src/mail/mail.service.ts中实现了用法,可以用作自定义的参考。

其他工具和信息

后端带有方便的开发工具,例如:

  1. Swagger-完整的API文档(http://localhost:3000/docs
  2. Adminer-数据库的客户端(http://localhost:8080
  3. MailDev -SMTP服务器(http://localhost:1080

请注意,这些服务应在生产中禁用!

在生产中运行

完成后端逻辑后,可以使用Docker在生产中运行它。您应该禁用上述开发工具。

请注意,如果您之前正在运行此样板,以获取代码,迁移等的最新更新。您首先必须重建Docker Image!

docker compose build --no-cache

默认情况下实现了记录,并且在./logs目录中可用日志。

结论

请注意,您可能需要在很多情况下编辑默认代码(例如,更改查询方法,更新当前DTO等),但这是任何项目的绝佳起点。

此外,此文档将根据需要进行更新或修复,并提供新功能,修复和改进。

问题和错误

请注意,此文档主要旨在加快您的入职过程。虽然它可能提供一些“快捷方式”解决方案,但这些解决方案纯粹是为了说明目的,以帮助阐明该样板的工作。如果您是否在前端或后端遇到任何问题,我们鼓励您在GitHub上提出一个问题: