简单的基于 CRUD 的模块是任何业务的共同需求,应该易于构建和维护。 Remult 是一个综合框架,允许开发人员仅使用 TypeScript 代码构建全栈、类型安全的应用程序。
本文将介绍 Remult 的基本概念,并将演示如何使用 Remult 来简化和加快您的 Web 应用程序开发过程!
在本指南中,我们将创建一个简单的预订表单,并将表单提交存储在 MongoDB 集合中。 我们将使用 React 构建 UI,然后使用 Spectre.css 添加样式。
跳跃前进
了解 Remult 框架
使用 Remult 设置 React 项目
初始化 remultExpress 中间件
在前端初始化 Remult
添加数据库连接
使用 Remult 实体生成 API 端点
构建和样式化前端
添加仅后端方法
Remult 是一个 CRUD 框架,它使用 TypeScript 实体进行 CRUD 操作。 它还提供了一个类型安全的 API 客户端和一个用于后端数据库操作的 ORM。
该框架抽象并减少了应用程序中的样板代码。 它使使用 TypeScript 构建全栈应用程序变得容易,还允许开发人员与其他框架集成,例如 Express.js 和 Angular。
Remult是一个中间立场。 它不会强迫你以某种方式工作。 相反,它为您的项目提供了许多选项。
让我们首先使用 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 开发服务器。
现在,让我们创建一个 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"));
我们将使用全局 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选项。
不要错过 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 端点、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 (
Book an appointment
);
}
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([]);
const [availableSlots, setAvailableSlots] = useState([]);
const [isSubmitting, setSubmitting] = useState(false);
const bookingDate = watch("date");
const onSubmit = async (values: Record) => {
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 (
);
};
在这里,我们使用 react-hook-form库来管理表单状态和输入值。
将提交的值保存在 bookings集合,我们需要为 Booking实体。
const bookingRepo = remult.repo(Booking);
Remult 存储库对象提供了对实体执行 CRUD 操作的方法。 在这种情况下,我们使用 save()将数据插入集合的存储库方法。
await bookingRepo.save(values);
有时,您可能希望创建具有附加逻辑的自定义 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 为 JavaScript 带来了类型安全。 类型安全和可读代码之间可能存在紧张关系。 观看录音,深入了解 TypeScript 4.4 的一些新功能。