extend existing API with custom endpoints

hrp*_*xQ4 12 javascript api rest express nestjs

I'm creating an API for multiple customers. The core endpoints like /users are used by every customer but some endpoints rely on individual customization. So it might be that User A wants a special endpoint /groups and no other customer will have that feature. Just as a sidenote, each customer would also use his own database schema because of those extra features.

I personally use NestJs (Express under the hood). So the app.module currently registers all my core modules (with their own endpoints etc.)

import { Module } from '@nestjs/common';

import { UsersModule } from './users/users.module'; // core module

@Module({
  imports: [UsersModule]
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

I think this problem is not related to NestJs so how would you handle that in theory?

I basically need an infrastructure that is able to provide a basic system. There are no core endpoints anymore because each extension is unique and multiple /users implementations could be possible. When developing a new feature the core application should not be touched. Extensions should integrate themselves or should get integrated on startup. The core system ships with no endpoints but will be extended from those external files.

Some ideas come to my mind


First approach:

Each extension represents a new repository. Define a path to a custom external folder holding all that extension projects. This custom directory would contain a folder groups with a groups.module

import { Module } from '@nestjs/common';

import { GroupsController } from './groups.controller';

@Module({
  controllers: [GroupsController],
})
export class GroupsModule {}
Run Code Online (Sandbox Code Playgroud)

My API could loop through that directory and try to import each module file.

  • pros:

    1. The custom code is kept away from the core repository
  • cons:

    1. NestJs uses Typescript so I have to compile the code first. How would I manage the API build and the builds from the custom apps? (Plug and play system)

    2. The custom extensions are very loose because they just contain some typescript files. Due to the fact they don't have access to the node_modules directory of the API, my editor will show me errors because it can't resolve external package dependencies.

    3. Some extensions might fetch data from another extension. Maybe the groups service needs to access the users service. Things might get tricky here.


Second approach: Keep each extension inside a subfolder of the src folder of the API. But add this subfolder to the .gitignore file. Now you can keep your extensions inside the API.

  • pros:

    1. Your editor is able to resolve the dependencies

    2. Before deploying your code you can run the build command and will have a single distribution

    3. You can access other services easily (/groups needs to find a user by id)

  • cons:

    1. When developing you have to copy your repository files inside that subfolder. After changing something you have to copy these files back and override your repository files with the updated ones.

Third approach:

Inside an external custom folder, all extensions are fully fledged standalone APIs. Your main API would just provide the authentication stuff and could act as a proxy to redirect the incoming requests to the target API.

  • pros:

    1. New extensions can be developed and tested easily
  • cons:

    1. Deployment will be tricky. You will have a main API and n extension APIs starting their own process and listening to a port.

    2. The proxy system could be tricky. If the client requests /users the proxy needs to know which extension API listens for that endpoint, calls that API and forwards that response back to the client.

    3. To protect the extension APIs (authentication is handled by the main API) the proxy needs to share a secret with those APIs. So the extension API will only pass incoming requests if that matching secret is provided from the proxy.


Fourth approach:

Microservices might help. I took a guide from here https://docs.nestjs.com/microservices/basics

I could have a microservice for the user management, group management etc. and consume those services by creating a small api / gateway / proxy that calls those microservices.

  • pros:

    1. New extensions can be developed and tested easily

    2. Separated concerns

  • cons:

    1. Deployment will be tricky. You will have a main API and n microservices starting their own process and listening to a port.

    2. It seems that I would have to create a new gateway api for each customer if I want to have it customizable. So instead of extending an application I would have to create a customized comsuming API each time. That wouldn't solve the problem.

    3. To protect the extension APIs (authentication is handled by the main API) the proxy needs to share a secret with those APIs. So the extension API will only pass incoming requests if that matching secret is provided from the proxy.

Esp*_*pen 5

有几种解决方法。您需要做的是找出最适合您的团队,组织和客户的工作流程。

如果这取决于我,我将考虑每个模块使用一个存储库,并使用像NPM这样的程序包管理器以及私有或组织范围的程序包来处理配置。然后设置构建发布管道,以推送到新构建的软件包仓库。

这样,您所需要做的就是主文件和每个自定义安装的软件包清单文件。您可以独立开发和部署新版本,也可以在需要时在客户端上加载新版本。

为了增加平滑度,您可以使用配置文件将模块映射到路由,并编写通用的路由生成器脚本来执行大多数引导。

由于包可以是任何东西,因此包内的交叉依赖关系将很容易解决。您只需要在更改和版本管理方面受到约束。

在此处阅读有关私有软件包的更多信息: 私有软件包NPM

现在,私人NPM注册管理机构要花钱,但是如果这是一个问题,那么还有其他几种选择。请查看本文,了解一些其他选择-免费和付费。

拥有私人npm注册表的方法

现在,如果您想推出自己的管理器,可以编写一个简单的服务定位器,该服务定位器将使用一个包含必要信息的配置文件,以从存储库中提取代码,进行加载,然后提供某种方法来检索实例。

我已经为这种系统编写了一个简单的参考实现:

框架:运动服务定位器

检查回文的插件示例locomotion插件示例

使用框架查找插件的应用程序locomotion应用程序示例

您可以通过从npm获取它来解决这个问题,npm install -s locomotion您将需要plugins.json使用以下架构指定文件:

{
    "path": "relative path where plugins should be stored",
    "plugins": [
        { 
           "module":"name of service", 
           "dir":"location within plugin folder",
           "source":"link to git repository"
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

例:

{
    "path": "./plugins",
    "plugins": [
        {
            "module": "palindrome",
            "dir": "locomotion-plugin-example",
            "source": "https://github.com/drcircuit/locomotion-plugin-example.git"
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

像这样加载它:const loco = require(“ locomotion”);

然后,它返回一个承诺,它将解决服务定位器对象,该对象具有locator方法来获取您的服务:

loco.then((svc) => {
    let pal = svc.locate("palindrome"); //get the palindrome service
    if (pal) {
        console.log("Is: no X in Nixon! a palindrome? ", (pal.isPalindrome("no X in Nixon!")) ? "Yes" : "no"); // test if it works :)
    }
}).catch((err) => {
    console.error(err);
});
Run Code Online (Sandbox Code Playgroud)

请注意,这只是参考实现,不足以用于严肃的应用程序。但是,该模式仍然有效,并显示了编写此类框架的要旨。

现在,这将需要扩展,以支持插件配置,初始化,错误检查,还可能添加对依赖项注入的支持等。