Code Monkey home page Code Monkey logo

express-idempotency's Introduction

Add idempotency to your express route, effortlessly, the way you want it.

Production (master)

CircleCI Codacy Badge Codacy Badge

Development branch (develop)

CircleCI Codacy Badge Codacy Badge

(Français)

Idempotency middleware for Express

Integrate idempotency feature to Express routes quickly, without efforts. The implementation is inspired from the Stripe idempotency implementation.

This is a Node.js module designed to work with Express, available through the NPM registry.

Features

  • Middleware for Express with a high level of customization
  • Helpers to detect processing from the middleware and report errors

Examples

For examples, check the examples folder.

Getting started

Install the dependency.

$ npm install express-idempotency

Integrate the middleware in your Express initialization.

// Javascript
const idempotency = require('express-idempotency');
// ...express initialization
app.post('*', idempotency.idempotency());

// Typescript
import { idempotency } from 'express-idempotency';
// ...express initialization
app.post('*', idempotency());

Determine a idempotency hit in route handler

The idempotency middleware will call the next function, whenever a idempotency key is detected or not. This is by design to avoid breaking the middleware and route handler chain. To prevent transaction processing to occurs, there is a hit function which determine if the idempotency middleware did process the request.

app.post('/', function (req, res) {
    // Check if there was a hit!
    const idempotencyService = getSharedIdempotencyService();
    if (idempotencyService.isHit(req)) {
        return;
    }

    // Do something
    res.send('Got a new POST request');
});

Report error in processing request

Sometimes, there is errors that can occur in the process of the request. In that case, the middleware must be aware of the error and remove any information related to the idempotency key that failed. You can notify the middleware by using the reportError function.

app.post('/', function (req, res) {
    const idempotencyService = getSharedIdempotencyService();

    // Processing...
    // Oh BOOM! There is a error. Let's notify the middleware.
    idempotencyService.reportError(req);
});

Options

You can configure the way the idempotency middleware will behave by providing options during initialization.

app.post(
    '*',
    idempotency({
        // Specify the header to be used to retrieve the idempotency key.
        idempotencyKeyHeader,
        // The data adapter used to store the resources.
        dataAdapter,
        // Logic to indicate if response must be kept for idempotency
        responseValidator,
        // Logic to detect misuse of the idempotency key
        intentValidator,
    })
);

Change the idempotency key header

By default, the header used to retrieve the idempotency key is Idempotency-Key but you can change it for any value you would like.

Data adapter

The data adapter allows to persist cached response for a idempotency key. The default implementation use in-memory storage, which is not recommended for production environment.

You can create your own data adapter by implementing the IIdempotencyDataAdapter interface but there is already some implementation ready.

Response validator

By default, a request will be considered successful and the response will be cached if the response http status code is between 200 and 299. If it is not the behavior that is wished, you can implement your own logic by providing your own responseValidator, which must implements the IIdempotencyResponseValidator interface.

export class CustomResponseValidator implements IIdempotencyResponseValidator {
    public isValidForPersistence(
        idempotencyResponse: IdempotencyResponse
    ): boolean {
        // Insert logic here
        // For example, we could cache any 500 status code.
        return idempotencyResponse.statusCode == 500;
    }
}

Intent validator

When receiving a request with an idempotency key, the middleware will compare it to the original request to ensure the intend. By default, the middleware is expecting a match for the url, the method, the query parameters and the body. If any of these element does not match, it will be qualified as a misuse of the idempotency key and an error will be thrown.

This intent validator can be override by providing your own implementation, if this is not the desired behavior. Simply implements the IIdempotencyIntentValidatorinterface.

export class CustomIntentValidator implements IIdempotencyIntentValidator {

  isValidIntent(req: express.Request, idempotencyRequest: IdempotencyRequest): boolean {
    // Insert logic here
    // For example, the url must match
    return req.url === idempotencyRequest.url;
  }

License

The source code of this project is distributed under the MIT License.

Contributing

See CONTRIBUTING.md.

Code of Conduct

Participation in this poject is governed by the Code of Conduct.

References

  1. Designing robust and predictable APIs with idempotency

(English)

Middleware d'idempotence pour Express

Ajouter à Express des fonctionnalités d'idempotence pour certaines routes rapidement, sans effort. L'implémentation est inspirée par la stratégie de Stripe.

Ce module Node.js est conçu pour fonctionner avec Express, et disponible sur le registre NPM.

Fonctionnalités

  • Middleware pour Express avec un haut niveau de configuration
  • Utilitaires pour permettre la détection de l'intervention du middleware et rapporter les erreurs

Exemples

Pour des exemples, voir le répertoire examples.

Démarrage rapide

Installation de la dépendance.

$ npm install express-idempotency

Intégrer le middleware dans l'initialisation de votre application Express.

// Javascript
const idempotency = require('express-idempotency');
// ...initialisation de Express
app.post('*', idempotency.idempotency());

// Typescript
import { idempotency } from 'express-idempotency';
// ...initialisation de Express
app.post('*', idempotency());

Déterminer l'intervention du middleware dans une route

Le middleware d'idempotence va faire l'appel de la fonction next, peu importe si la clé d'idempotence est détecté ou non. Cette décision de design permet de conserver la chaine de communication quant à la prise en charge de la requête. Afin de prévenir une transaction d'être exécuté une deuxième fois, il y a une fonction hit permettant d'identifier le traitement préalable du middleware.

app.post('/', function (req, res) {
    // Vérifier l'intenvention!
    const idempotencyService = getSharedIdempotencyService();
    if (idempotencyService.isHit(req)) {
        return;
    }

    // Faire quelque chose
    res.send('Got a new POST request');
});

Rapporter des erreurs de traitement

Quelques fois, on veut communiquer au middleware que le traitement attendu a échoué. Dans ces cas, il faut que le middleware sache qu'une erreur est survenue et retirer l'information relative à la clé d'idempotence courante. On peut donc notifier le middleware en utilisant la fonction reportError.

app.post('/', function (req, res) {
    const idempotencyService = getSharedIdempotencyService();

    // Processing...
    // Oh BOOM! There is a error. Let's notify the middleware.
    idempotencyService.reportError(req);
});

Options

Vous pouvez configurer lors de l'initialisation la façon dont le middleware va se comporter.

app.post(
    '*',
    idempotency({
        // Préciser l'entête de requête contenant la clé d'idempotence.
        idempotencyKeyHeader,
        // L'adapteur de données utilisé pour stocker les informations.
        dataAdapter,
        // Préciser la logique permettant d'indiquer si la réponse doit être conservé pour l'idempotence.
        responseValidator,
        // Préciser la logique à appliquer pour s'assurer de la bonne utilisation de la clé d'idempotence.
        intentValidator,
    })
);

Changer la clé d'entête de requête

Par défaut, l'entête utilisé pour la clé d'idempotence est Idempotency-Key mais elle peut être changé pour une autre valeur désirée.

Adapteur de données

L'adapteur de données permet de persister l'information relatif à la clé d'idempotence. L'implémentation par défaut conserve l'information en mémoire, ce qui n'est pas recommandé pour un environnement de production.

Vous pouvez créer votre propre adapteur de données par l'implémentation de l'interface IIdempotencyDataAdapter. Les implémentations connues sont les suivantes :

Validateur de réponse

Par défaut, une requête est considéré valide lorsque le statut http est entre 200 et 299 inclusivement. À ce moment-là, la réponse est persistée. Si ce n'est pas le comportement attendu, il est possible d'implémenter sa propre logique en fournissant votre propre validateur de réponse. À ce moment-là, il faut implémenter l'interface IIdempotencyResponseValidator.

export class CustomResponseValidator implements IIdempotencyResponseValidator {
    public isValidForPersistence(
        idempotencyResponse: IdempotencyResponse
    ): boolean {
        // Insert logic here
        // For example, we could cache any 500 status code.
        return idempotencyResponse.statusCode == 500;
    }
}

Validateur d'intention

Lorsqu'on reçoit une requête avec une clé d'idempotence, le middleware va comparer celle-ci avec la requête originale ayant généré une ressource idempotente afin d'assurer l'intention. Par défaut, le middleware va s'assurer que l'adresse (url), la méthode, les paramètres de requête et le contenu. Si l'un de ces éléments divergent, la requête va être considéré comme invalide et l'utilisation de la clé d'idempotence incorrecte.

Ce comporement peut être remplacé en fournissant son propre validateur d'intention par l'implémentation de l'interface IIdempotencyIntentValidator.

export class CustomIntentValidator implements IIdempotencyIntentValidator {

  isValidIntent(req: express.Request, idempotencyRequest: IdempotencyRequest): boolean {
    // Insérer logique ici
    // Par exemple, seulement l'adresse doit correspondre
    return req.url === idempotencyRequest.url;
  }

Contribuer

Voir CONTRIBUTING.md

Licence et propriété intellectuelle

Le code source de ce projet est libéré sous la licence MIT License.

Code de Conduite

La participation à ce projet est réglementée part le Code de Conduite

Références

  1. Designing robust and predictable APIs with idempotency

express-idempotency's People

Contributors

dmmulroy avatar joscelynjean avatar lawp09 avatar psionikangel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

express-idempotency's Issues

Async the database connection using express-idempotency-mongo-adapter

For of all thank you very much for this library which is really helpful, I am using it with the express-idempotency-mongo-adapter and it works perfectly.

Although I integrated it within some specific routes of my api located under a Google Cloud Function, and it create many extras connection to the database even though the specific routes are not called.

How could review the idempotency service to create database connection, only when the actual routes using the idempotency service gets called ?

Thanks in advance for your recommandations.

autobind-decorator should be a runtime dependency

When I follow the example code here: https://github.com/VilledeMontreal/express-idempotency/blob/master/examples/simple-use/src/index.js in my own application, I get the following stack trace:

`internal/modules/cjs/loader.js:883
throw err;
^

Error: Cannot find module 'autobind-decorator'
Require stack:

  • /home/lavid/Documents/wcp-order-backend/node_modules/express-idempotency/dist/services/idempotencyService.js
  • /home/lavid/Documents/wcp-order-backend/node_modules/express-idempotency/dist/index.js
  • /home/lavid/Documents/wcp-order-backend/server.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:880:15)
    at Function.Module._load (internal/modules/cjs/loader.js:725:27)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    `

It seems that autobind-decorator should be a runtime dependency?

"autobind-decorator": "^2.4.0",

findByIdempotencyKey: return type

Hey,

The function findByIdempotencyKey in IIdempotencyDataAdapter has the return type Promise<IdempotencyResource>. However, the InMemoryDataAdapter returns null which doesn't fit. Can you please clarify?

Thanks

Response.writeHead never called when client timeout and close the connection

When the client timeout occurs and the TCP connection is closed the Nodejs response.writeHead() function is not automatically called. In that case, the writeHeadHook Promise is never resolved and the IdempotencyResponse is never built. We would like to persist the response in the memory and the adapter.

Deprecation Warning on "._headers"

Hello,

The package seems to work as intended, but produces the following deprecation warning:

(node:12797) [DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated

For reference, node provides the following explanation on the matter.

I attempted to fix it myself using node's recommended public method, but can't get the project to compile as it produces the following errors.

> [email protected] compile
> ./node_modules/.bin/tsc --build tsconfig.dist.json

node_modules/@types/express-serve-static-core/index.d.ts:589:18 - error TS2430: Interface 'Response<ResBody, Locals, StatusCode>' incorrectly extends interface 'ServerResponse'.
  Property 'req' is optional in type 'Response<ResBody, Locals, StatusCode>' but required in type 'ServerResponse'.

589 export interface Response<
                     ~~~~~~~~

node_modules/@types/express/ts4.0/index.d.ts:43:55 - error TS2344: Type 'Response<any, Record<string, any>>' does not satisfy the constraint 'ServerResponse'.
  Property 'req' is optional in type 'Response<any, Record<string, any>>' but required in type 'ServerResponse'.

43     var static: serveStatic.RequestHandlerConstructor<Response>;
                                                         ~~~~~~~~


Found 2 errors.

To get the following messages, I cloned the repo, ran npm install and then npm run compile. I'll will probably look further at the cause of those errors at some point later if you don't have the time to do so, but I expect your knowledge of the project might speed up resolving this issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.