Express-Like Middleware Engine
Hard · Express
An Express app is a list of middleware functions. For each request Express walks the list in registration order and calls the functions whose path matches. A function calls next() to pass the request on, or sends a response and stops. Implement a simplified version of that engine.
class App:
- use(fn) or use(path, fn): middleware for every method. Without a path (or with "/") it matches every request. "/api" matches /api and paths starting with /api/, but not /apix.
- get(path, fn) / post(path, fn): only that method and the exact path. A segment like :id matches any single segment and is stored in req.params: /users/:id matches /users/42 with req.params = { id: "42" }. For use middleware, req.params is {}.
- handle(req): req is { method, path, log }. Create a res object with status(code) (returns res so calls can be chained) and send(body). The status defaults to 200. Return { status, body } after the request was handled.
Running a request:
- A function with 4 parameters, (err, req, res, next), is an error handler. Check fn.length === 4, exactly like Express does. Other functions are normal handlers.
- While there is no error, only matching normal handlers run. Calling next(err) with an argument, or throwing inside a handler, switches to error mode: from then on only matching error handlers run, and they receive the error.
- In an error handler, next(err) passes the error on, and next() without an argument clears the error and goes back to normal handlers.
- If the end of the list is reached without a response: in error mode respond 500 with body "Internal Server Error", otherwise 404 with body "Cannot <METHOD> <path>".
Everything is synchronous here. The driver runApp(spec, requests) is already written: it turns each [kind, path, action] of spec into a real handler (see makeHandler in the starter; for example "log:A" pushes "A" to req.log and calls next(), and "catch:E" is an error handler that responds 500 with "E: <message>") and registers it (path is null for use without a path). It returns { status, body, log } for every [method, path] request.