比人工智能更快地构建 Express API

遵守软件原则可确保项目长寿,并能够更快地交付高质量代码 - 通常比任何代码生成工具都快。

我们能否在一分钟内构建代码库以交付 API 或端点?简短的回答是:是的,这是可能的。

通过遵循干净代码原则,我们可以设计高效的 ExpressJS、MongoDB 和 TypeScript API。结合函数式编程和面向对象编程,我们将创建一个易于开发且协作愉快的代码库,从而提高效率和积极的开发体验。

技术堆栈:

  • ExpressJS:API 端点
  • TypeScript:类型安全性和稳健性
  • Mongoose:MongoDB 交互
  • 代码结构:

  • 模型:定义实体模式
  • 实体:核心逻辑和契约
  • BaseRepository:常见的数据库操作
  • BaseService:共享业务逻辑
  • BaseController:常见的CRUD操作
  • AsyncControllerCreator:生成具有错误处理的异步控制器
  • RouterCreator:为实体创建路由器
  • ValidatorCreator:验证实体特定的端点
  • 这种架构促进了可重用、可扩展的组件,确保快速交付功能,同时保持未来变化的灵活性。

    我们的文件夹结构将反映这种方法。

    Image description

    因此,这里有一件事需要真正理解,即 generic.ts 文件,例如在存储库中,Repository 类是通用类,它可以扩展任何存储库,并具有数据库的查找、创建、更新和删除方法所需的功能,它可以接受任何模型,这就是我们创建可重用逻辑的方式,在 JS 中,我们可以使用 mixin 进行多重继承,以进一步扩展派生类。

    要了解的第二件事是 index.ts 文件,我们在其中创建应用程序实体存储库、服务和控制器的实例。

    完整的源代码可以在这里找到:https://github.com/ARAldhafeeri/ExpressJS-1minute-crud

    让我们解释一下,

    第一个全局类型

    import { Request, Response, Express, NextFunction } from 'express';
    
    declare global {
        type APIResponse = { data: Data; message: string; status: boolean };
        type Controller = (req: Request, res: Response) => void;
        type Middleware = (req: Request, res: Response, next: NextFunction) => void;
    }

    在这里我们创建了可重用的全局类型以供跨源代码使用。

    实用程序/ControllerCreator.ts

    这有助于我们在编写代码并通过抛出错误返回错误时删除多余的错误处理并专注于实现。

    import { Request, Response } from 'express';
    
    export const ControllerCreator =
        (fn: Controller ) => (req: Request, res: Response) => {
            Promise.resolve(fn(req, res)).catch((err: Error) => {
                res.status(400).send({ status: false, message: `Error: ${err.message}` });
            });
        };

    实用程序/RouterCreator

    这有助于我们创建冗余的 curd api,我们可以创建多个这样的 api,这个 api 需要一个控制器,我们可以创建一个需要一个路由器的 api,并将其扩展为跨不同实体的可重用端点。请注意我们如何返回路由器,因此我们可以使用该实体特定的端点进行扩展。

    import { Router } from 'express';
    import {IController} from "../entities/generic";
    
    export const RouterCreator = (controller: IController): Router => {
        const router = Router();
    
        controller.fetch &&  router.get('/', (req, res) => controller.fetch!(req, res));
        controller.create && router.post('/', (req, res) => controller.create!(req, res));
        controller.update && router.put('/', (req, res) => controller.update!(req, res));
        controller.delete &&  router.delete('/', (req, res) => controller.delete!(req, res));
    
        return router;
    };

    请注意,只有当控制器在其实现中定义端点时,它才会获取端点。

    存储库/generics.ts

    在这里我们创建了基础仓库,注意在子类中我们可以访问模型,并使用与子类相关的功能进一步扩展它

    import { IRepository } from '../entities/generic';
    import {Model, Document, UpdateQuery} from 'mongoose';
    import { FilterQuery, ProjectionType, Types } from 'mongoose';
    
    export class Repository implements IRepository {
        constructor(private model: Model) {}
    
        async find(
            filter: FilterQuery,
            projection: ProjectionType,
        ): Promise {
            return this.model.find(filter, projection)
                .sort({ updatedAt: -1 })
        }
    
        async create(record: T): Promise {
            const newRecord = new this.model(record);
            return newRecord.save();
        }
    
        async update(
            filter: FilterQuery,
            record: UpdateQuery,
        ): Promise {
            return this.model.findByIdAndUpdate(filter, record, { new: true }).exec();
        }
    
        async delete(filter: FilterQuery): Promise {
            return this.model.findOneAndDelete(filter).exec();
        }
    }

    实体/泛型.ts

    import {FilterQuery, ProjectionType, Types, UpdateQuery} from 'mongoose';
    
    export interface IBaseEntity {
        _id?: Types.ObjectId;
        createdAt?: Date;
        updatedAt?: Date;
    }
    
    
    export interface IRepository {
        find(
            filter: FilterQuery,
            projection: ProjectionType,
        ): Promise;
        create(record: T): Promise;
        update(
            filter: FilterQuery,
            record: UpdateQuery,
        ): Promise;
        delete(
            filter: FilterQuery,
        ): Promise;
    }
    
    export interface IService {
        find(id: string ): Promise;
        create(record: T, id: string): Promise;
        update(
            record: T,
            recordID: string
        ): Promise;
    
        delete(id: string, organization: string): Promise;
    }
    
    export interface IController {
        fetch ?: Controller;
        create?: Controller;
        update?: Controller;
        delete?: Controller;
        search?: Controller;
    }

    服务/generic.ts

    这里我们创建基础服务,与 repo 相同,我们可以通过 super 访问 repo

    import { IService, IBaseEntity, IRepository } from '../entities/generic';
    import {Types} from "mongoose";
    import ObjectId = Types.ObjectId;
    
    export class Service implements IService {
        constructor(protected repository: IRepository) {
        }
    
        async find(organization: string ): Promise {
            const filter = { organization: organization };
            return  this.repository.find(filter, {});
    
        }
    
        async create(record: T, organization: string): Promise {
            return this.repository.create(record);
        }
    
        async update(
            record: T,
            recordID: string
        ): Promise {
            const filter = {
                _id: new ObjectId(recordID),
            }
            return this.repository.update(filter, record);
        }
    
        async delete(id: string): Promise {
            return this.repository.delete({_id: id});
        }
    
    }

    控制器/generic.ts

    import { Request, Response } from 'express';
    import {ControllerCreator} from "../utils/ControllerCreator";
    
    class Controller {
        constructor(protected service: T) {
            this.service = service;
        }
    
        fetch = ControllerCreator(async (req: Request, res: Response) => {
    
            const data = await (this.service as any).find();
            res.status(200).json({ data, status: true, message: 'Data fetched' });
        });
    
        create = ControllerCreator(async (req: Request, res: Response) => {
            const data = await (this.service as any).create(req.body);
            res.status(201).json({ data, status: true, message: 'Created successfully' });
        });
    
        update = ControllerCreator(async (req: Request, res: Response) => {
            const data = await (this.service as any).update(req.body, req.query.id as string);
            res.status(200).json({ data, status: true, message: 'Updated successfully' });
        });
    
        delete = ControllerCreator(async (req: Request, res: Response) => {
            const data = await (this.service as any).delete(req.query.id as string);
            res.status(200).json({ data, status: true, message: 'Deleted successfully' });
        });
    }
    
    export default Controller;

    我们已经为我们的基本存储库、服务、控制器创建了基本契约,以供其他类继承。

    现在是结果,我们可以用少于 10 行代码为多个实体发送多个 curd API,请注意每个目录内的 index.ts 文件我们只需创建实例,因此我们的应用程序是内存优化的。

    实体/用户.ts

    我们为用户创建了合同,同时也保持其开放以供扩展。

    import {IBaseEntity, IController, IRepository, IService} from "./generic";
    
    export interface IUser extends IBaseEntity {
        name: string;
        address: string;
    }
    
    export interface IUserRepository extends IRepository {
    
    }
    
    export interface IUserService extends IService{
    
    }
    
    export interface IUserController extends IController {}

    模型/用户.ts

    简单的猫鼬模型

    import { Schema, model } from 'mongoose';
    import { IUser } from '../entities/user';
    
    export const userSchema = new Schema(
        {
            name: { type: String },
            address: { type: String },
        },
        { timestamps: true }
    );
    
    userSchema.index({ name: 'text', address: 'text' });
    
    export default model('User', userSchema);

    存储库/用户.ts

    我们使用从基础存储库中查找、创建、更新、删除的核心功能,并且保持用户存储库开放以供扩展

    import { Repository } from "./generic";
    import {IUser, IUserRepository} from "../entities/user";
    
    
    class UserRepository extends Repository implements IUserRepository {
    
    }
    
    export default UserRepository;

    服务/用户.ts

    import {Service} from "./generic";
    import {IUser, IUserService} from "../entities/user";
    
    
    class UserService extends Service implements IUserService {
    
    }
    
    export default UserService;

    控制器/用户.ts

    import Controller from "./generic";
    import {IUserController, IUserService} from "../entities/user";
    
    
    class UserController extends Controller implements IUserController {
    
    }
    
    export default UserController;

    路线/用户.ts

    import {RouterCreator} from "../utils/RouterCreator";
    import {userController} from "../controllers";
    
    const UserRouter = RouterCreator(userController);
    
    export default UserRouter;

    应用程序

    import express, {Application} from "express";
    import UserRouter from "./routes/user";
    
    
    const App : Application = express();
    
    App.use("/user", UserRouter);
    
    export default App;

    我希望你能明白它有多么强大,我们可以在源代码中、在任何层上创建多个可重用的代码块,这将带来健壮的源代码和非常易于维护、可读、无错误的开发人员体验。

    此致,

    艾哈迈德,