extend existing API with custom endpoints

There are several approaches to this. What you need to do is figure out what workflow is best suited for your team, organization, and clients.

If this was up to me, I would consider using one repository per module, and use a package manager like NPM with private or organization scoped packages to handle the configuration. Then set up build release pipelines that push to the package repo on new builds.

This way all you need is the main file, and a package manifest file per custom installation. You can independently develop and deploy new versions, and you can load new versions when you need to on the client-side.

For added smoothness, you could use a configuration file to map modules to routes and write a generic route generator script to do most of the bootstrapping.

Since a package can be anything, cross dependencies within the packages will work without much hassle. You just need to be disciplined when it comes to change and version management.

Read more about private packages here: Private Packages NPM

Now Private NPM registries cost money, but if that is an issue there are several other options as well. Please review this article for some alternatives - both free and paid.

Ways to have your private npm registry

Now if you want to roll your own manager, you could write a simple service locator, that takes in a configuration file containing the necessary information to pull the code from the repo, load it up, and then provide some sort of method to retrieve an instance to it.

I have written a simple reference implementation for such a system:

The framework: locomotion service locator

An example plugin checking for palindromes: locomotion plugin example

An application using the framework to locate plugins: locomotion app example

You can play around with this by getting it from npm using npm install -s locomotion you will need to specify a plugins.json file with the following schema:

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

example:

{
    "path": "./plugins",
    "plugins": [
        {
            "module": "palindrome",
            "dir": "locomotion-plugin-example",
            "source": "https://github.com/drcircuit/locomotion-plugin-example.git"
        }
    ]
}

load it like this: const loco = require("locomotion");

It then returns a promise that will resolve the service locator object, which has the locator method to get a hold of your services:

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

Please note that this is just a reference implementation, and is not robust enough for serious application. However, the pattern is still valid and shows the gist of writing this kind of framework.

Now, this would need to be extended with support for plugin configuration, initializations, error checking, maybe add support for dependency injection and so on.


I would go for external packages option.

You can structure your app to have a packages folder. I would have UMD compiled builds of external packages in that folder so that your compiled typescript won't have any issues with the packages. All packages should have an index.js file on each package's root folder.

And your app can run a loop through the packages folder using fs and require all the packages index.js into your app.

Then again dependency installation is something you have to take care of. I think a configuration file on each package could solve that too. You can have a custom npm script on main app to install all the package dependencies before starting the application.

This way, you can just add new packages to your app by copy pasting the package into the packages folder and rebooting the app. Your compiled typescript files won't be touched and you don't have to use private npm for your own packages.