Remult:使用 TypeScript 构建一个类型安全的全栈应用程序

简单的基于 CRUD 的模块是任何业务的共同需求,应该易于构建和维护。 Remult 是一个综合框架,允许开发人员仅使用 TypeScript 代码构建全栈、类型安全的应用程序。

本文将介绍 Remult 的基本概念,并将演示如何使用 Remult 来简化和加快您的 Web 应用程序开发过程!

在本指南中,我们将创建一个简单的预订表单,并将表单提交存储在 MongoDB 集合中。 我们将使用 React 构建 UI,然后使用 Spectre.css 添加样式。

跳跃前进

  • 了解 Remult 框架

  • 使用 Remult 设置 React 项目

  • 初始化 remultExpress 中间件

  • 在前端初始化 Remult

  • 添加数据库连接

  • 使用 Remult 实体生成 API 端点

  • 构建和样式化前端

  • 添加仅后端方法

了解 Remult 框架

Remult 是一个 CRUD 框架,它使用 TypeScript 实体进行 CRUD 操作。 它还提供了一个类型安全的 API 客户端和一个用于后端数据库操作的 ORM。

该框架抽象并减少了应用程序中的样板代码。 它使使用 TypeScript 构建全栈应用程序变得容易,还允许开发人员与其他框架集成,例如 Express.js 和 Angular。

Remult是一个中间立场。 它不会强迫你以某种方式工作。 相反,它为您的项目提供了许多选项。

使用 Remult 设置 React 项目

让我们首先使用 Create React App 创建一个 React 项目并选择 TypeScript 模板:

> npx create-react-app remult-react-booking-app --template typescript
> cd remult-react-booking-app

接下来,我们将安装所需的依赖项。

> npm i axios express remult dotenv
> npm i -D @types/express ts-node-dev concurrently

在上面的代码中,我们使用 concurrently包裹。 这个包是必需的,因为我们将从 React 项目的根目录同时提供客户端和服务器代码。


超过 20 万开发人员使用 LogRocket 来创造更好的数字体验 了解更多 →


现在,创建一个 tsconfig服务器的文件,如下所示:

// tsconfig.server.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs",
    "emitDecoratorMetadata": true
  }
}

然后,在主要 tsconfig.json文件,添加 experimentalDecorators启用装饰器的选项。

// tsconfig.json
​
{
  "compilerOptions": {
    // ...
    "experimentalDecorators": true
  },
}

更新 package.json文件,像这样:

// package.json
​
{
  "proxy": "http://localhost:3002",
  // ...
  "scripts": {
  // ...
    "start:dev": "concurrently -k -n \"SERVER,WEB\" -c \"bgBlue.bold,bgGreen.bold\" \"ts-node-dev -P tsconfig.server.json src/server/\" \"react-scripts start\""
  },
}

在这里,我们添加 proxy当应用程序在本地环境中运行时,让 webpack 开发服务器知道在端口 3000 到 3002 上代理 API 请求的选项。 我们还添加了一个 npm 脚本来同时启动前端和 API 开发服务器。

初始化 remultExpress中间件

现在,让我们创建一个 server里面的文件夹 src由 Create React App 创建的文件夹并创建一个 api.ts将初始化的文件 remultExpress中间件。

// src/server/api.ts
​
import { remultExpress } from "remult/remult-express";
​
export const api = remultExpress();

接下来,创建一个 .env服务器的文件并指定 API 端口号。

// src/server/.env
​
API_PORT=3002

接下来,创建一个 index.ts将作为服务器根文件的文件,初始化 express,加载环境变量,并注册 remultExpress中间件。

// src/server/index.ts
​
import { config } from "dotenv";
config({ path: __dirname + "/.env" });
​
import express from "express";
import { api } from "./api";
​
const app = express();
app.use(api);
​
app.listen(process.env.API_PORT || 3002, () => console.log("Server started"));

在前端初始化 Remult

我们将使用全局 RemultReact 应用程序中的对象通过 axiosHTTP 客户端。

// src/common.ts
​
import axios from "axios";
import { Remult } from "remult";
​
export const remult = new Remult(axios);

至此,主项目设置完成,可以在本地服务器上运行。

使用以下命令:

> npm run start:dev

添加数据库连接

在本指南中,我们将使用 MongoDB 来存储我们的表单提交。 要为 Remult 设置 MongoDB 连接池,请使用 remultExpress中间件的 dataProvider选项。


来自 LogRocket 的更多精彩文章:

  • 不要错过 The Replay 来自 LogRocket 的精选时事通讯

  • 了解 LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题

  • 使用 React 的 useEffect 优化应用程序的性能

  • 之间切换 在多个 Node 版本

  • 了解如何 使用 AnimXYZ 为您的 React 应用程序制作动画

  • 探索 Tauri ,一个用于构建二进制文件的新框架

  • 比较 NestJS 与 Express.js


首先,你必须安装 mongodb作为项目中的依赖项,如下所示:

> npm i mongodb

这 dataProvider选项可以接受 async()连接到 MongoDB 并返回 MongoDataProvider对象,它充当 Remult 的连接器。

// src/server/api.ts
​
import { MongoDataProvider } from "remult/remult-mongo";
​
export const api = remultExpress({
  dataProvider: async () => {
    const client = new MongoClient(process.env.MONGO_URL || "");
    await client.connect();
    console.log("Database connected");
    return new MongoDataProvider(client.db("remult-booking"), client);
  },
});

使用 Remult 实体生成 API 端点

Remult 使用实体来生成 API 端点、API 查询和数据库命令。 entity用作前端和后端代码的模型类。

我们将需要两个实体来定义预订对象和可用的每日时段。

创建一个 shared里面的文件夹 src,它将包括在前端和后端之间共享的代码。 然后,创建另一个子文件夹用于存储实体 shared文件夹,并创建实体类文件: Booking.entity.ts和 Slot.entity.ts.

要创建实体,请定义具有所需属性的类,然后使用 @Entity装饰师。 这 @Entity装饰器接受一个基本参数,用于确定 API 路由、默认数据库集合或表名,以及一个用于定义实体相关属性和操作的选项参数。

对于本指南, Slot实体可以定义如下:

// src/shared/entities/Slot.entity.ts
​
import { Entity, Fields, IdEntity } from "remult";
​
@Entity("slots")
export class Slot extends IdEntity {
  @Fields.string()
  startTime: String;
​
  @Fields.string()
  endTime: String;
}

这 @Fields.string装饰器定义类型的实体数据字段 String. 此装饰器还用于描述与字段相关的属性,例如验证规则和操作。

// src/shared/entities/Booking.entity.ts
​
import { Entity, Fields, IdEntity, Validators } from "remult";
​
@Entity("bookings", {
  allowApiCrud: true
})
export class Booking extends IdEntity {
  @Fields.string({
    validate: Validators.required,
  })
  name: String;
​
  @Fields.string({
    validate: Validators.required,
  })
  email: String;
​
  @Fields.string({ validate: Validators.required })
  description: String;
​
  @Fields.string({
    validate: Validators.required,
  })
  date: String;
​
  @Fields.string({
    validate: Validators.required,
  })
  slotId: string;
}

现在两个实体都已定义,让我们将它们添加到 remultExpress中间件的 entities财产。 我们还可以使用 initApi财产。

// src/server/api.ts
​
import { Slot } from "../shared/entities/Slot.entity";
import { Booking } from "../shared/entities/Booking.entity";
​
export const api = remultExpress({
  entities: [Slot, Booking],
  initApi: async (remult) => {
    const slotRepo = remult.repo(Slot);
    const shouldAddAvailablSlots = (await slotRepo.count()) === 0;
​
    if (shouldAddAvailablSlots) {
      const availableSlots = [10, 11, 12, 13, 14, 15, 16, 17].map((time) => ({
        startTime: `${time}:00`,
        endTime: `${time}:45`,
      }));
​
      await slotRepo.insert(availableSlots);
    }
  },
  dataProvider: async () => {
    // ...
  },
});

构建和样式化前端

让我们通过构建表单 UI 开始处理应用程序的前端。

首先,替换默认的样板代码 src/App.tsx包含以下代码的文件:

// src/App.tsx
​
import "./App.css";
import { BookingForm } from "./components/BookingForm";
​
function App() {
  return (
    <div className="App">
      <header className="hero hero-sm bg-primary ">
        <div className="hero-body text-center">
          <div className="container grid-md">
            <h1>Book an appointment</h1>
          </div>
        </div>
      </header>
      <BookingForm />
    </div>
  );
}
​
export default App;

现在,让我们添加 Spectre.css 库以使用户界面看起来像样。

> npm i spectre.css

您可以参考以下代码 BookingForm零件:

// src/components/BookingForm.tsx
​
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { remult } from "../common";
import { Booking } from "../shared/entities/Booking.entity";
import { Slot } from "../shared/entities/Slot.entity";
​
const bookingRepo = remult.repo(Booking);
​
export const BookingForm = () => {
  const {
    register,
    handleSubmit,
    setValue,
    watch,
    setError,
    clearErrors,
    reset,
    formState: { errors },
  } = useForm();
​
  const [availableDates, setAvailableDates] = useState<string[]>([]);
  const [availableSlots, setAvailableSlots] = useState<Slot[]>([]);
​
  const [isSubmitting, setSubmitting] = useState<boolean>(false);
​
  const bookingDate = watch("date");
​
  const onSubmit = async (values: Record<string, any>) => {
    try {
      setSubmitting(true);
      const data = await bookingRepo.save(values);
      console.log({ data });
      reset();
    } catch (error: any) {
      setError("formError", {
        message: error?.message,
      });
    } finally {
      setSubmitting(false);
    }
  };
​
  // JSX code
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <>...</>
    </form>
  );
};

在这里,我们使用 react-hook-form库来管理表单状态和输入值。

将提交的值保存在 bookings集合,我们需要为 Booking实体。

const bookingRepo = remult.repo(Booking);

Remult 存储库对象提供了对实体执行 CRUD 操作的方法。 在这种情况下,我们使用 save()将数据插入集合的存储库方法。

await bookingRepo.save(values);

新上架IOS影视神器,伪装上架支持投屏,画质都是4K蓝光级别!

添加仅后端方法

有时,您可能希望创建具有附加逻辑的自定义 API,例如发送电子邮件、执行多个数据库操作或完成其他顺序任务。

多个数据库操作只能在后端执行,因为在前端拥有各种实体级功能可能会影响应用程序的性能。

在 Remult 中实现仅后端方法的一种方法是创建一个控制器类并使用 @BackendMethod装饰师。

对于我们项目的预订表单,让我们创建两个后端方法。 第一种方法, getAvailableDates(),将获得接下来的五个可用工作日。 第二种方法, getAvailableSlots(), 将按日期获取可用的预订时段。

// src/shared/controllers/Booking.controller.ts
​
import { BackendMethod, Remult } from "remult";
import { Booking } from "../entities/Booking.entity";
import { Slot } from "../entities/Slot.entity";
import { addWeekDays, formattedDate } from "../utils/date";
​
export class BookingsController {
  @BackendMethod({ allowed: true })
  static async getAvailableDates() {
    const addDates = (date: Date, count = 0) =>
      formattedDate(addWeekDays(date, count));
​
    return Array.from({ length: 5 }).map((v, idx) => addDates(new Date(), idx));
  }
​
  @BackendMethod({ allowed: true })
  static async getAvailableSlots(date: string, remult?: Remult) {
    if (!remult) return [];
    const unavailableSlotIds = (
      await remult.repo(Booking).find({ where: { date } })
    ).map((booking) => booking.slotId);
​
    const availableSlots = await remult
      .repo(Slot)
      .find({ where: { id: { $ne: unavailableSlotIds } } });
​
    return availableSlots;
  }
}

这 allowed中的财产 @BackendMethod装饰器定义请求用户是否有权访问 API。 在这种情况下,这是真的,因为我们希望 API 是公开的。

您可以拥有控制价值的授权规则 allowed财产。 后端方法也可以访问 remult对象以执行数据库操作。

要使用后端方法,您不必手动进行任何 API 调用。 只需在前端代码中导入控制器并直接调用方法,就像对任何其他模块一样。

在内部,Remult 使用您在初始化 Remult 时在前端代码中定义的 HTTP 客户端为您进行 API 调用。 这样,您就可以保证 API 是类型安全的并且更易于维护。

// src/components/BookingForm.tsx
​
import { BookingsController } from "../shared/controllers/Booking.controller";
​
export const BookingForm = () => {
   // ...
  useEffect(() => {
    BookingsController.getAvailableDates().then(setAvailableDates);
  }, []);
​
  useEffect(() => {
    if (!availableDates.length) return;
    setValue("date", availableDates[0]);
    BookingsController.getAvailableSlots(availableDates[0]).then(
      setAvailableSlots
    );
  }, [availableDates]);
​
  useEffect(() => {
    BookingsController.getAvailableSlots(bookingDate).then(setAvailableSlots);
  }, [bookingDate]);
​
  useEffect(() => {
    setValue("slotId", availableSlots[0]?.id);
  }, [availableSlots]);
 // ...
}

如下所示, 日期 和 可用 下拉表单字段现在默认预先填写。

如果我们尝试提交不完整值的表单,则添加的验证规则 Booking实体将失败并返回错误。

要查看本文中的完整代码,请参阅 GitHub 存储库 。

结论

Remult 是一个很棒的框架,可让您快速轻松地构建类型安全的全栈应用程序。 其简单的语法使 Remult 成为任何希望开始类型安全编程的开发人员的完美工具。 您可以查看 官方文档 以更深入地了解本指南中涵盖的方法。

那你还在等什么? 今天就试试 Remult!

写了很多 TypeScript? 观看 我们最近的 TypeScript 聚会的录像,了解如何编写更具可读性的代码。

TypeScript 为 JavaScript 带来了类型安全。 类型安全和可读代码之间可能存在紧张关系。 观看录音,深入了解 TypeScript 4.4 的一些新功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pxr007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值