Skip to content

Instantly share code, notes, and snippets.

@HuakunShen
Last active December 6, 2022 04:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HuakunShen/ab8d50cbc2cb819ad21af00d27b135a9 to your computer and use it in GitHub Desktop.
Save HuakunShen/ab8d50cbc2cb819ad21af00d27b135a9 to your computer and use it in GitHub Desktop.
Express.js-Style Middleware Design using Chain of Responsibility

Express-Style Middleware Design

This gist demonstrates how to implement a expressjs-style middleware design with Chain of Responsibility design pattern.

expressjs middleware has the following structure

app.use((req, res, next) => {
  ...
  next();
)

I was wondering how the next() call back works and how to make the middlewares execution synchronous.

I composed 2 sample code. simple-demo.ts is a simple example.

register-route.ts includes a route handler.

/**
* This file demonstrates express.js-style middleware design, using Chain of Responsibility design pattern
* Suppose we are building an app with only one route (/register)
* Note: all middlewares in this sample code are only for /register route, for demonstration purpose only, middlewares in real life are more general
*/
type MiddlewareRequest = { body: any };
type MiddlewareResponse = { success: boolean; error?: string };
type Middleware = (
req: MiddlewareRequest,
res: MiddlewareResponse,
next: () => void
) => void;
type RouteHandler = (req: MiddlewareRequest, res: MiddlewareResponse) => void; // a route handler doesn't need a next function
class App {
middlewares: Middleware[];
routeHandlers: Map<string, RouteHandler>;
constructor() {
this.middlewares = [];
this.routeHandlers = new Map();
}
use(middleware: Middleware) {
this.middlewares.push(middleware);
}
run(route: string, req: MiddlewareRequest, res: MiddlewareResponse) {
res.success = true; // init with success = true
res.error = undefined;
const middlewares = [...this.middlewares]; // make a copy of middlewares, used as a queue/stack
const next = () => {
const middleware = middlewares.shift();
if (middleware) middleware!(req, res, next);
};
next();
if (res.success) {
// middlewares did not throw any error
const handler = this.routeHandlers.get(route);
if (handler) handler(req, res);
} else {
// middlewares not successful
console.log(`~~~~~~ Error: ${res.error} ~~~~~~`);
}
}
post(route: string, handler: RouteHandler) {
this.routeHandlers.set(route, handler);
}
}
// initialize app (middleware container)
const app = new App();
// add parameter check
app.use((req, res, next) => {
if (!req.body.username) {
res.success = false;
res.error = "Missing username in request body";
return;
}
if (!req.body.password) {
res.success = false;
res.error = "Missing password in request body";
return;
}
next();
});
// add password length check middleware
app.use((req, res, next) => {
if (req.body.password.length < 8) {
res.success = false;
res.error = "Password has to be at least 8 characters";
return;
}
next();
});
// add username length check
app.use((req, res, next) => {
if (req.body.username.length < 6) {
res.success = false;
res.error = "Username has to be at least 6 characters";
return;
}
next();
});
// register a route
app.post("/register", (req, res) => {
console.log(
`Registered user ${req.body.username} with password: ${req.body.password}`
);
});
// how the app looks like now
console.log(app);
// initlize payload and response object
const req = {
route: "/register",
body: {
username: "user",
} as any,
};
const res: MiddlewareResponse = { success: true };
// run the app
app.run("/register", req, res);
// output: ~~~~~~ Error: Missing password in request body ~~~~~~
req.body.password = "pass";
app.run("/register", req, res);
// ~~~~~~ Error: Password has to be at least 8 characters ~~~~~~
req.body.username = "longusername";
app.run("/register", req, res);
// ~~~~~~ Error: Password has to be at least 8 characters ~~~~~~
req.body.password = "password";
app.run("/register", req, res);
// Registered user longusername with password: password
type MiddlewareRequest = { message: string; middlewareCount: number };
type MiddlewareResponse = { success: boolean; error?: string };
type Middleware = (
req: MiddlewareRequest,
res: MiddlewareResponse,
next: () => void
) => void;
class App {
middlewares: Middleware[];
constructor() {
this.middlewares = [];
}
use(middleware: Middleware) {
this.middlewares.push(middleware);
}
run(req: MiddlewareRequest, res: MiddlewareResponse) {
const middlewares = [...this.middlewares]; // make a copy of middlewares, used as a queue/stack
const next = () => {
const middleware = middlewares.shift();
if (middleware) middleware!(req, res, next);
};
next();
}
}
// initialize app (middleware container)
const app = new App();
// add middlewares
app.use((req, res, next) => {
req.message += " (m1)";
req.middlewareCount++;
console.log(`middleware 1: ${req.message}`);
next();
});
app.use((req, res, next) => {
req.message += " (m2)";
req.middlewareCount++;
console.log(`middleware 2: ${req.message}`);
res.success = false;
res.error = "Error happens in middleware 2";
next();
});
// how the app looks like now
console.log(app);
// initlize payload and response object
const req = { message: "Some Random Message", middlewareCount: 0 };
const res: MiddlewareResponse = { success: true };
// run the app
app.run(req, res);
// result
console.log("Results");
console.log(`\tMiddleware Count: ${req.middlewareCount}`);
console.log(`\tSucecss: ${res.success}`);
if (res.error) console.log(`\tError: ${res.error}`);
/** Console Output
* middleware 1: Some Random Message (m1)
* middleware 2: Some Random Message (m1) (m2)
* Results
* Middleware Count: 2
* Sucecss: false
* Error: Error happens in middleware 2
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment