• 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 (
        
         
           
             
               

    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);

    新上架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 的一些新功能。

  • 相关阅读:
    鸿蒙APP开发页面组件之间的属性关系
    macos 中ios系统升级,但是macos还是老系统,在手机上无法安装ios软件
    java毕业设计音乐社交平台设计Mybatis+系统+数据库+调试部署
    【Unity Shader】顶点片元着色器01 基本命令介绍
    生产制造业ERP管理系统能解决哪些问题?
    【重拾C语言】六、批量数据组织(三)数组初值;字符串、字符数组、字符串数组;类型定义 typedef
    力扣 -- 712. 两个字符串的最小ASCII删除和
    【C++程序员必修第一课】C++基础课程-09:while 循环
    ArcGIS_创建随机点
    使用mklink来节省C盘空间
  • 原文地址:https://blog.csdn.net/weixin_47967031/article/details/127136448