Skip to content

Commit

Permalink
Merge pull request #140 from sliit-foss/feature/express-http-context
Browse files Browse the repository at this point in the history
Feat!: initialized express-http-context package
  • Loading branch information
Akalanka47000 authored Jan 23, 2024
2 parents 5fb335d + ce6be37 commit e22e26a
Show file tree
Hide file tree
Showing 7 changed files with 608 additions and 7 deletions.
36 changes: 36 additions & 0 deletions packages/express-http-context/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@sliit-foss/express-http-context",
"version": "0.0.0",
"description": "A rework of the express-http-context package to use the new async_hooks API",
"main": "dist/index.js",
"browser": "dist/browser.js",
"types": "types/index.d.ts",
"scripts": {
"build": "node ../../scripts/esbuild.config.js",
"build:watch": "bash ../../scripts/esbuild.watch.sh",
"bump-version": "bash ../../scripts/bump-version.sh --name=@sliit-foss/express-http-context",
"lint": "bash ../../scripts/lint.sh",
"release": "bash ../../scripts/release.sh",
"test": "bash ../../scripts/test/test.sh"
},
"devDependencies": {
"express": "^4.16.2"
},
"author": "SLIIT FOSS",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/sliit-foss/npm-catalogue.git"
},
"homepage": "https://github.com/sliit-foss/npm-catalogue/blob/main/packages/express-http-context/readme.md",
"keywords": [
"express",
"context",
"http-context",
"bun",
"bun.js"
],
"bugs": {
"url": "https://github.com/sliit-foss/npm-catalogue/issues"
}
}
71 changes: 71 additions & 0 deletions packages/express-http-context/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# @sliit-foss/express-http-context

#### A rework of the [express-http-context](https://www.npmjs.com/package/express-http-context) package to use the new async_hooks API <br><br>

**Built as the existing package does not work alongside the [Bun](https://bun.sh/) runtime**

---

Get and set request-scoped context anywhere. This is just an unopinionated, idiomatic ExpressJS implementation of [AsyncLocalStorage](https://nodejs.org/api/async_context.html#new-asynclocalstorage). It's a great place to store user state, claims from a JWT, request/correlation IDs, and any other request-scoped data. Context is preserved even over async/await.

## How to use it

Install: `bun install --save express-http-context`

Use the middleware immediately before the first middleware that needs to have access to the context.
You won't have access to the context in any middleware "used" before this one.

Note that some popular middlewares (such as body-parser, express-jwt) may cause context to get lost.
To workaround such issues, you are advised to use any third party middleware that does NOT need the context
BEFORE you use this middleware.

```js
const express = require("express");
const httpContext = require("express-http-context");

const app = express();
// Use any third party middleware that does not need access to the context here, e.g.
// app.use(some3rdParty.middleware);
app.use(httpContext.middleware);
// all code from here on has access to the same context for each request
```

Set values based on the incoming request:

```js
// Example authorization middleware
app.use((req, res, next) => {
userService.getUser(req.get("Authorization"), (err, result) => {
if (err) {
next(err);
} else {
httpContext.set("user", result.user);
next();
}
});
});
```

Get them from code that doesn't have access to the express `req` object:

```js
const httpContext = require("express-http-context");

// Somewhere deep in the Todo Service
function createTodoItem(title, content, callback) {
const user = httpContext.get("user");
db.insert({ title, content, userId: user.id }, callback);
}
```

You can access the store directly as follows

```js
const store = require("express-http-context").store;
```

## Troubleshooting

For users of Node below version 14

1. Unfortunatly, this package does not work with any version of Node below 14. This is due to the fact that the AsyncLocalStorage API was introduced in Node 14. If you are using Node below version 14, you can use the original [express-http-context](https://www.npmjs.com/package/express-http-context) package but as of writing this, it will not work with [Bun](https://bun.sh/).
21 changes: 21 additions & 0 deletions packages/express-http-context/src/browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* eslint no-unused-vars: 0 */

export const middleware = (req, res, next) => {
throw new Error("`middleware` cannot be called from the browser code.");
};

export const get = () => null;

export const set = (key, value) => {};

export const store = null;

export const ns = null;

export default {
middleware,
get,
set,
store: null,
ns: null
};
42 changes: 42 additions & 0 deletions packages/express-http-context/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use strict";

import { AsyncLocalStorage } from "async_hooks";

export const store = new AsyncLocalStorage();

/** Express.js middleware that is responsible for initializing the context for each request. */
export const middleware = (req, res, next) => {
store.run({}, () => {
next();
});
};

/**
* Gets a value from the context by key. Will return undefined if the context has not yet been initialized for this request or if a value is not found for the specified key.
* @param {string} key
*/
export const get = (key) => {
const storeData = store.getStore() || {};
return storeData[key];
};

/**
* Adds a value to the context by key. If the key already exists, its value will be overwritten. No value will persist if the context has not yet been initialized.
* @param {string} key
* @param {*} value
*/
export const set = (key, value) => {
const storeData = store.getStore() || {};
storeData[key] = value;
store.enterWith(storeData);
};

export const ns = null;

export default {
middleware,
get: get,
set: set,
store: store,
ns: null
};
22 changes: 22 additions & 0 deletions packages/express-http-context/test/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { middleware, set, get } from "../src";
import { middleware as browserMiddleware, set as browserSet, get as browserGet } from "../src/browser";

describe("test store", () => {
beforeAll(() => {
middleware({}, {}, () => {});
});
test("set and retrieve a value", () => {
set("foo", "bar");
expect(get("foo")).toBe("bar");
});
});

describe("test browser store", () => {
beforeAll(() => {
expect(() => browserMiddleware({}, {}, () => {})).toThrowError();
});
test("set and retrieve a value", () => {
browserSet("foo", "bar");
expect(browserGet("foo")).toBe(null);
});
});
25 changes: 25 additions & 0 deletions packages/express-http-context/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Request, Response, NextFunction } from "express";
import { AsyncLocalStorage } from "async_hooks";

/** Express.js middleware that is responsible for initializing the context for each request. */
export declare function middleware(req: Request, res: Response, next: NextFunction): void;

/**
* Gets a value from the context by key. Will return undefined if the context has not yet been initialized for this request or if a value is not found for the specified key.
*/
export declare function get(key: string): any;

/**
* Adds a value to the context by key. If the key already exists, its value will be overwritten. No value will persist if the context has not yet been initialized.
*/
export declare function set(key: string, value: any): void;

/**
* Gets the underlying store.
*/
export declare const store: AsyncLocalStorage<any>;

/**
* @deprecated Since `async_hooks` uses a store instead of namespaces, this is no longer available. This property will be removed in a future release.
*/
export declare const ns: null;
Loading

0 comments on commit e22e26a

Please sign in to comment.