In part 2, We applied the middleware pattern for our web framework, which make us able to execute how many middlewares that we want for a route.
Today We are going to create Decorators for our Request
and Response
objects, applying the decorator
and middleware
pattern together to get parameters and query strings from the URL, also create helper functions to respond to the requests as a JSON transformer, for example.
Let's go!
Request Decorator
src/request.js
const { match } = require('path-to-regexp')
const RequestDecorator = (routes, request, response, next) => {
// (1)
const getParams = () => {
const urlParams = request.url.split('/').slice(1)
// remove querystrings from the last parameter
const [lastParam] = urlParams[urlParams.length - 1].split('?')
urlParams.splice(urlParams.length - 1, 1)
// joining all params without querystrings
const allParams = [...urlParams, lastParam].join('/')
// matching url pattern to get the params
for (const path of routes) {
const urlMatch = match(path, {
decode: decodeURIComponent,
});
const url = `/${allParams}/${request.method.toUpperCase()}`
const found = urlMatch(url)
if (found) {
Object.keys(found.params).forEach(key => {
request.params = {
...request.params,
[key]: found.params[key]
}
})
break
}
}
}
// (2)
const getQuery = () => {
const urlParams = request.url.split('/').slice(1)
// Isolating query strings
const [lastParam, queryString] = urlParams[urlParams.length - 1].split('?')
let params = new URLSearchParams(queryString);
let entries = params.entries();
request.query = {
...request.query,
...Object.fromEntries(entries)
}
}
getParams()
getQuery()
next()
}
module.exports = RequestDecorator
From the code sessions above:
1 - Treating the URL to compare with the existent routes and get the parameters and set the request
object.
2 - Transforming the query string into an object and setting the request
object.
Response Decorator
src/response.js
const ResponseDecorator = (req, res, next) => {
// (1)
res.status = (status) => {
res.statusCode = status
return res
}
// (2)
res.json = (data) => {
res.setHeader('Content-type', 'application/json')
res.end(JSON.stringify(data))
}
// (3)
res.send = async (data) => {
res.end(data)
}
res.render = async (templatePath, data) => {
// We are going to implement it later
}
next()
}
module.exports = ResponseDecorator
From the code sessions above:
1 - Changing the status code and returning the response
object, to set the status and response in one line.
2 - Transforming the response into JSON.
3 - Send a text to the client.
Applying Decorators as Middlewares
src/app.js
We need to import our two decorators:
const requestDecorator = require('./request')
const responseDecorator = require('./response')
Now, We have to modify our serverHandler
function to apply the decorators as middlewares for all routes.
const serverHandler = async (request, response) => {
const sanitizedUrl = sanitizeUrl(request.url, request.method)
const match = matchUrl(sanitizedUrl)
if (match) {
const middlewaresAndControllers = routes.get(match)
await dispatchChain(request, response,
[requestDecorator.bind(null, routes.keys()), responseDecorator, ...middlewaresAndControllers])
} else {
response.statusCode = 404
response.end('Not found')
}
}
Testing
index.js
// (1)
app.get('/params/:id/:name', (req, res) => {
res.end(JSON.stringify({ params: req.params, query: req.query }, null, 2))
})
// (2)
app.get('/response/:id', (req, res) => {
if (req.params.id === '123') {
res.status(200).json(req.params)
return
}
res.status(400).json({ message: 'Invalid id' })
})
From the code sessions above:
1 - Returning the parameters and query strings from the URL as text.
2 - Here is an example of how you can return JSON and change the status code at the same time.
Conclusion
Using the same that We use for middlewares, we can treat the resquest and response and make them fancier.
You can take a look at the entire code here
I hope you like it.
Top comments (1)
Nice bro