|
/** |
|
* 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 |