简单的基于 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 的一些新功能。