Enhance the current API functionality by incorporating personalized endpoints

Developing an API for multiple clients presents a unique challenge. While core endpoints like /users are common across all clients, some features may require individual customization. For instance, one client (e.g. "User A") may request a specialized endpoint like /groups, exclusive to their use only. Additionally, each client will have their own database schema due to these custom features.

In my case, I prefer using NestJs with Express. The app.module currently manages all core modules and their respective endpoints.

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

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

@Module({
  imports: [UsersModule]
})
export class AppModule {}

This issue isn't specifically related to NestJs, so how would you theoretically address this situation?

The solution requires an infrastructure that can support a basic system. Instead of having predefined core endpoints, the application should be able to adapt to unique extensions introduced by different clients. New features should seamlessly integrate without altering the core application. Extensions must self-integrate or be integrated upon startup. The core system itself won't have any predefined endpoints but will be extended externally.

Several approaches come to mind:


First approach:

Each extension acts as a new repository within a custom external folder dedicated to housing these extension projects. This directory contains separate folders for each extension, such as a groups folder with its own groups.module.

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

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

@Module({
  controllers: [GroupsController],
})
export class GroupsModule {}

The API could scan through this directory and import each module file accordingly.

  • pros:

    1. Keeps custom code separate from the core repository.
  • cons:

    1. You need to compile Typescript files as NestJs uses Typescript. How would you manage building the API alongside the custom app builds? (Plug-and-play system)
    2. Custom extensions are somewhat isolated, lacking access to the main API's node_modules directory. This might lead to editor errors due to unresolved package dependencies.
    3. Certain extensions may require data from other extensions, which complicates inter-extension communication.

Second approach:

Store each extension within a subfolder of the API's src directory while adding it to the .gitignore file. This allows you to keep extensions within the API.

  • pros:

    1. Editor can resolve dependencies more effectively.
    2. Prior to deployment, running the build command results in a unified distribution.
    3. Accessing other services is simplified (e.g. /groups interacting with user data).
  • cons:

    1. While developing, you must copy repository files into the subfolder. After making changes, you'll need to override the repository files with updated versions.

Third approach:

In an external custom folder, consider treating each extension as a standalone API. The main API would handle authentication and serve as a proxy to route incoming requests to the designated API.

  • pros:

    1. Eases development and testing of new extensions.
  • cons:

    1. Deployment complexities arise from having a main API and multiple independent extension APIs operating on various ports.
    2. Implementing a reliable proxy system is crucial, especially when redirecting requests based on specific endpoints.
    3. To secure extension APIs, the proxy must share a secret key with these APIs to validate incoming requests.

Fourth approach:

Microservices offer another viable option. Utilizing a guide like the one found here https://docs.nestjs.com/microservices/basics, you could create microservices for components like user management and group management. These services can be consumed by a gateway or proxy configured within the API.

  • pros:

    1. Simplifies development and testing of new features.
    2. Establishes clear separation of concerns.
  • cons:

    1. Deploying a main API along with multiple microservices introduces complexity, each potentially listening on separate ports.
    2. Creating a customized consuming API for each customer may not fully address the initial problem of extending the application efficiently.
    3. Securing extension APIs requires collaboration between the proxy and the targeted APIs using shared secrets for validation.

Answer №1

When it comes to managing workflows, there are various approaches you can take. The key is to determine the workflow that best fits your team, organization, and clients.

In my opinion, using one repository per module and leveraging a package manager like NPM with private or organization scoped packages for configuration management could be beneficial. Setting up build release pipelines to push new builds to the package repo ensures smooth deployment processes.

This approach simplifies things as you only need the main file and a package manifest file per custom installation. It allows independent development and deployment of new versions, enabling easy loading of new versions on the client-side when required.

To enhance efficiency, consider utilizing a configuration file to map modules to routes and create a generic route generator script for streamlined bootstrapping.

Cross dependencies within the packages can work seamlessly with this setup, provided you maintain discipline in change and version management practices.

If using Private NPM registries involves costs, there are alternative options available. Explore this article for insights into free and paid alternatives:

Discover ways to set up your private npm registry

For those interested in creating a customized manager, developing a simple service locator that retrieves necessary information from a configuration file to access code from the repo and provide instances could be a viable option.

Check out a reference implementation of such a system here:

The framework: locomotion service locator

Example plugin for checking palindromes: locomotion plugin example

Application utilizing the framework for locating plugins: locomotion app example

You can experiment with this by installing it via npm using npm install -s locomotion. You will also need to specify a plugins.json file following a specific schema.

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

For instance:

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

To load it, simply use: const loco = require("locomotion");

It returns a promise that resolves the service locator object, offering the locator method to access 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);
});

Note that this implementation serves as a reference and may require enhancements for robust application usage, such as support for plugin configuration, error checking, dependency injection, and more.

Overall, this pattern demonstrates the essence of building a framework of this nature, emphasizing adaptability and extensibility for future developments.

Answer №2

My preference would be to opt for external packages.

To organize your application, consider creating a dedicated packages folder. Within this folder, store UMD compiled builds of external packages to avoid any compatibility issues with your TypeScript compilation. Each package should include an index.js file in its root directory.

Your application can then iterate through the packages folder using fs, utilizing require to import all index.js files into your app.

Managing dependencies is crucial in this setup. You may address this by including a configuration file within each package. Create a custom npm script in the main app to install all package dependencies prior to running the application.

This approach allows you to seamlessly integrate new packages by copying them into the packages folder and restarting the app. Your TypeScript files remain unaffected, eliminating the need for a private npm repository for in-house packages.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Incorrect spacing in the toLocaleTimeString method in Microsoft Edge's JavaScript implementation

I'm struggling to comprehend the behavior of a JavaScript code that appears to function differently in Edge. Here's what I've narrowed it down to: var testi = new Date().toLocaleTimeString(); var len2 = testi.length; alert(len2); In Edge, ...

Integrating VueJs into a pre-existing .net core 5 project

I am currently working on a .net core solution that consists of 9 different projects including api, dto, data, service, etc. I now have the requirement to incorporate a project that utilizes the Vue.js framework for the frontend within this existing .net ...

The infinite loop issue arises when the useEffect is utilizing the useNavigate hook

As I integrate the useNavigate hook within React to guide users to a specific page post-login, an unexpected infinite loop arises. Most sources online recommend utilizing the useNavigate hook inside a useEffect block; however, this method triggers a Warnin ...

You are only able to click the button once per day

I am working on a button that contains numeric values and updates a total number displayed on the page when clicked. I would like this button to only be clickable once per day, so that users cannot click it multiple times within a 24 hour period. Below i ...

Incorporate additional information into the current data series within Lightning Chart

In my React JS application, I have successfully used a point series to create a chart displaying data. However, I am now looking to bind real-time data to the chart. For example, when the chart initially loads with 10 points, I want to continuously receiv ...

Navigating through web pages and creating dynamic interfaces is made simple

As someone relatively new to React, I am currently exploring how to integrate React Router with Material UI. In my Layout file, I have implemented a Navigation drawer from Material UI with the menu on the left and content on the right, as depicted in the ...

Svelte is unable to bring in

Hey there, I'm new to Svelte and currently working on a simple feedback app. I have divided my project into two files - one for the main app and another for a list of feedbacks. Here is my App.svelte file: <script> import feedback from ". ...

Exploring Several Images and Videos in Angular

I'm experiencing a challenge with displaying multiple images and videos in my Angular application. To differentiate between the two types of files, I use the "format" variable. Check out Stackblitz export class AppComponent { urls; format; on ...

Transmitting an array within the POST request payload

My goal is to send an array to my Node/MongoDB server using an AJAX POST request, along with other variables in the body. Check out the client side JS code below: function setupForm(){ $("#add").submit(function(event) { event.preventDefault() ...

Overriding filters in AngularJS and injecting dependencies into modules

My application relies on two filter modules: var app = angular.module('MyApp',['Filter1','Filter2']); Both modules contain filters with the same name: var filterapp1 = angular.module('Filter1',[]); filterapp1.f ...

Retrieve the initial image link from a blogger's post in cases where it is not being stored on the Blogger platform for use in related

I am currently using a hosting service to store the images that I include on my blogger platform. However, I have encountered an issue where blogger does not automatically fetch the image url to use as the thumbnail when the image is hosted externally. C ...

AngularJS - "Refrain from replicating items in a repeater"

I am facing an issue with creating HTML textarea elements for each member of an array. Despite consulting the AngularJS documentation and attempting different track by expressions, I am unable to render them. The problem arises when a user inputs the same ...

Unable to dial phone numbers when clicking 'Click to call' link on Android devices

Within my Cordova android application, I have a link set up like this: <a href = "tel:011123456789">Click to Call</a> While this click-to-call functionality works smoothly in IOS, it seems to be hindered in Android by an issue such as: 11- ...

express.js without changing the output

Imagine we have a controller that retrieves data from a third-party service, saves it to a database, and sends a response to the client: const findUser = async (req, res, next) => { if (req.params.username) { const resp_ = await fetchFunction (re ...

Improving JavaScript code for checking arrays and objects

When I receive data, it could be an array of objects or just a single object. I have written some code but I believe there is a way to improve it - make it more elegant, concise, and error-free. Here is the current code snippet: export const CalculateIt = ...

Load CSS asynchronously or with defer to reduce page rendering time

I've been focusing on optimizing my site speed score and working to enhance my Pagespeed by addressing the unused CSS issue. Initially, I followed the recommendations on this page: load CSS simpler. Despite deferring the CSS file using the following m ...

Unable to retrieve data with fetch in react despite receiving a 200 status code

I am currently facing an issue with the fetch function in my React code: componentDidMount() { this.userService.getLoggedInUser() .then(user => { this.setState({user: user}); console.log(this.state.user); }) } ...

How can a script be properly embedded into an HTML document?

Currently, I am facing an unusual issue with the script tags in my Django project. My layout.html file includes Jquery and Bootstrap in the head section. Using Jinja, I extended layout.html to create a new file called main.html. In main.html, I added a new ...

Unable to navigate through images on Bootstrap Carousel using previous and next buttons

Despite following tutorials and examining the HTML code on bootstrap, I'm facing issues while trying to create a carousel. I believed that everything was done correctly, but when I click on the next button, nothing happens. <!DOCTYPE html> < ...

Can you help me make a JavaScript Random Number Generator that utilizes HTML input fields and buttons?

I am currently working on developing a random number generator that takes user input through HTML. The idea is to have the user enter two values and then click "submit" to receive a random number within that range. However, I seem to be stuck at this poin ...