
Creating Your Own ExpressJS from Scratch (Part 3) - Handling Request and Response Objects
In Part 2, we implemented middleware chaining to allow multiple functions to be executed per route. Now, in Part 3, we’ll improve the developer experience by decorating the Request and Response objects.
We'll extract route parameters and query strings, and define helper methods like res.status()
and res.json()
— just like in ExpressJS.
🧪 What You Will Learn
- Use decorators to enhance the
req
andres
objects - Extract route parameters and query strings from incoming requests
- Create convenient response helpers like
.status()
and.json()
🏗️ Request Decorator
The request decorator adds params
and query
properties to the request object.
src/request.js
const { match } = require('path-to-regexp');
const RequestDecorator = (routes, req, res, next) => {
const getParams = () => {
const urlParts = req.url.split('/').slice(1);
const [lastPart] = urlParts[urlParts.length - 1].split('?');
urlParts.splice(urlParts.length - 1, 1);
const fullPath = [...urlParts, lastPart].join('/');
const fullUrl = `/${fullPath}/${req.method.toUpperCase()}`;
for (const path of routes) {
const urlMatch = match(path, { decode: decodeURIComponent });
const found = urlMatch(fullUrl);
if (found) {
req.params = { ...req.params, ...found.params };
break;
}
}
};
const getQuery = () => {
const [_, query] = req.url.split('?');
if (query) {
const params = new URLSearchParams(query);
req.query = Object.fromEntries(params.entries());
}
};
getParams();
getQuery();
next();
};
module.exports = RequestDecorator;
✅ What This Does:
req.params
: Extracted from route pattern like/users/:id
req.query
: Extracted from URL query string like?sort=desc
🚀 Response Decorator
Let’s define some Express-style helpers on the response object.
src/response.js
const ResponseDecorator = (req, res, next) => {
res.status = (code) => {
res.statusCode = code;
return res;
};
res.json = (data) => {
res.setHeader('Content-type', 'application/json');
res.end(JSON.stringify(data));
};
res.send = (data) => {
res.end(data);
};
res.render = (templatePath, data) => {
// Future implementation
};
next();
};
module.exports = ResponseDecorator;
✅ What This Enables:
res.status(200).json({ msg: 'ok' })
res.send('plain text response')
🔌 Registering Decorators in the App
Update your src/app.js
to apply these decorators automatically.
const requestDecorator = require('./request');
const responseDecorator = require('./response');
Modify the serverHandler
:
const serverHandler = async (req, res) => {
const sanitizedUrl = sanitizeUrl(req.url, req.method);
const match = matchUrl(sanitizedUrl);
if (match) {
const handlers = routes.get(match);
await dispatchChain(
req,
res,
[
requestDecorator.bind(null, routes.keys()),
responseDecorator,
...handlers,
]
);
} else {
res.statusCode = 404;
res.end('Not found');
}
};
Now, every request will be decorated with .params
, .query
, .json()
, and more.
🧪 Testing Parameter and Query Extraction
index.js
app.get('/params/:id/:name', (req, res) => {
res.json({ params: req.params, query: req.query });
});
app.get('/response/:id', (req, res) => {
if (req.params.id === '123') {
return res.status(200).json({ message: 'Valid ID' });
}
res.status(400).json({ message: 'Invalid ID' });
});
Test these routes:
GET http://localhost:3000/params/42/john?sort=desc
GET http://localhost:3000/response/123
📚 Summary
- You created request and response decorators
- You now support
req.params
,req.query
, and helpers likeres.json()
- These enhancements make your framework feel much more like Express
▶️ Coming Up Next
In Part 4, we’ll add support for dynamic route matching with path variables, and begin thinking about response rendering.
Follow along for the next steps in building your own web framework! 🚀