express.js - how to intercept response.send() / response.json() - javascript

Lets say I have multiple places where I call response.send(someData). Now I want to create a single global interceptor where I catch all .send methods and make some changes to someData. Is there any way in express.js? (hooks, listeners, interceptors, ...)?

You can define a middleware as below (taken and modified from this answer)
function modifyResponseBody(req, res, next) {
var oldSend = res.send;
res.send = function(data){
// arguments[0] (or `data`) contains the response body
arguments[0] = "modified : " + arguments[0];
oldSend.apply(res, arguments);
}
next();
}
app.use(modifyResponseBody);

for those finding on google, based off the top answer:
app.use((req, res, next) => {
const oldSend = res.send
res.send = function(data) {
console.log(data) // do something with the data
res.send = oldSend // set function back to avoid the 'double-send'
return res.send(data) // just call as normal with data
}
next()
})

Yes this is possible. There are two ways to do this, one is to use a library that provides the interception, with the ability to run it based on a specific condition:
https://www.npmjs.com/package/express-interceptor
The other option is to just create your own middleware (for express) as follows:
function modify(req, res, next){
res.body = "this is the modified/new response";
next();
}
express.use(modify);

Just want to provide a practical usage example with intercepting res.json.
When we're writing express server, we might send the status and message in every response according to the situation like below.
app.post('/test', (req, res) => {
res.json({status: 1, message: "some_error", otherData: "nothing"})
})
But, what if I don't want to write the status and message in every time? I could add new function to build a template response body to send the data when using res.json.
const messageMap = {
0: "success",
1: "some_error"
}
app.use((req, res, next) => {
const originJson = res.json
res.json = (status, jsonData) => {
const fixedResponse = {
status,
message: messageMap[status]
}
originJson.call(res, {...jsonData, ...fixedResponse})
}
next()
})
Then I just need to use below function.
app.get("/test", (req, res) => {
res.json(1, {otherData: 1})
})
You can even use builder pattern to do this.
app.use((req, res) => {
res.buildFixedResponse = function (status) {
const fixedResponse = {
status,
message: messageMap[status]
}
res.json = function (jsonData) {
originJson.call(this, {...jsonData, ...fixedResponse})
}
return this
}
})
Then trigger function like below.
app.get("/test", (req, res) => {
res.buildFixedResponse(1).json({otherData: 1})
})

For my case, I had to use a middleware with typicode/json-server and be able to get a different response object than just a blunt javascript array.
While the typicode/json-server response was something like:
[
{
...
}
]
After applying the middleware:
module.exports = (req, res, next) => {
const oldSend = res.send;
res.send = (data) => {
const oldData = JSON.parse(data);
// we want to change the response only if a blunt array is sent
// also, we do this for the old sake of not sending json arrays
if(Object.prototype.toString.call(oldData) === '[object Array]') {
data = {
data: oldData
};
}
res.send = oldSend;
return res.send(data);
};
next();
}
The response looks as follows:
{
data: [
{
...
}
]
}

You can simply do it using NODEJS and Express, say you are calling an API and want to send modify the data before sending response back.
router.get('/id', (req,res) => {
... //your code here filling DATA
let testData = {
"C1": "Data1",
"C2": "Data2",
"yourdata": DATA
};
res.send(testData);
});

Related

How to wait for a variable to be populated by an api request before passing it to a webpage as an argument?

I'm new to JavaScript and cannot seem to make this work , the topic of quiz depends on the user input... when the user presses next , I get the topic (this also takes user to the main quiz page), then i have to fetch data from the api with the topic as a parameter... I have to process the result of the fetch operation.. Then I have to pass that info to to the main quiz page... but the variable that is supposed to be populated by the fetch request is still undefined when i pass is to the main quiz page
var Allquestions;
var sheetdb = require('sheetdb-node');
// create a config file
var config = {
address: 'https://sheetdb.io/api/v1/9djmf8ydc7hwy',
};
//sheetdb
// Create new client
var client = sheetdb(config);
function downloadquestions(topic) {
console.log(topic);
client.read({ limit: 2, sheet: topic }).then(function(data) {
console.log(data + " in client.read func")
processQuestions(data);
}, function(err){
console.log(err);
});
}
async function processQuestions(data) {
console.log(data + "data in process");
Allquestions = JSON.parse(data);
console.log(Allquestions[0].Question + " This is defined");
}
app.get("/", (req, res) => {
res.render("pages/index", { title: "Home"});
});
// app.post("/" , urlencodedParser ,(req , res) => {
// console.log(req.body.topic);
// })
app.get("/questions", urlencodedParser , (req , res) => {
downloadquestions(req.body.topic);
console.log(Allquestions + " this is undefined");
res.render("/pages/quizpage" , {Allquestions})
})
There are a few issues with your code, you have a broken promise chain, client.read( is a promise, and that promise is going nowhere. You either return it, or await it. To be able to await your will need to also mark your route (req, res) as async too.
Your code is a little mixed up, you have Allquestions as a global var, this isn't great for multi-user, as the last topic is going to override this each time.
Also try and avoid swallowing exceptions in utility functions, try and keep your exception handling at the top level, eg. in your case inside your req/res handler.
So with all this in mind, your refactored code could look something like ->
const sheetdb = require('sheetdb-node');
// create a config file
const config = {
address: 'https://sheetdb.io/api/v1/9djmf8ydc7hwy',
};
//sheetdb
// Create new client
const client = sheetdb(config);
async function downloadquestions(topic) {
const data = await client.read({ limit: 2, sheet: topic });
return processQuestions(data);
}
function processQuestions(data) {
return JSON.parse(data);
}
app.get("/", (req, res) => {
res.render("pages/index", { title: "Home"});
});
app.get("/questions", urlencodedParser , async (req , res) => {
try {
const allQuestions = await downloadquestions(req.body.topic);
res.render("/pages/quizpage" , {Allquestions});
} catch (e) {
console.error(e);
res.end('There was an error');
}
})

Express JS - Including the optional parameter not working

I have a problem with doing Pagination. when including multiple & parameters. simply say, it doesn't work.
server.get("/search", async(req, res) => {
try {
const key = req.query.key;
const value = req.query.value;
const text = req.query.text;
let result = await collection.aggregate([
{
'$search': {
'text': {
'query': `${text}`,
'path': 'title'
}
}
},
//match key here...
]).toArray();
res.send(result)
} catch (error) {
console.error(error)
}
})
The problem you have there is how you structured your endpoint url.
app.get("/search/:text/:key?&:value?&.....", (req,res)) => {....}
If you want to get the values you send via query string, you don't have to add the query params to the endpoint's url, you can simply have it like so:
app.get("/search", (req,res)) => {....}
And then build the request to the API like this:
http://localhost:4000/search?text=mango&brand=rasna
Like this you can access the properties of the request in the route's controller:
app.get("/search", (req,res)) => {
const { text, brand } = req.query;
}
Optional values are fine, if you don't send them they will be undefined when you try to access their values in the controller so you can just check for them with conditionals.
app.get("/search", (req, res)) => {
const { text, brand } = req.query;
if(text) { ... }
if(brand) { ... }
}
Obviously this is a very simple implementation just to explain the problem.

Can not do get request method because data says [object object]

I'm trying to do a get request method in Vue and Express to get data based on my v-model.
Below is my code that tries to send data to express.
getResult() {
axios
.get(
`${process.env.VUE_APP_API}/hospita/result/` +
{
hosp_name: "SAMPLE"
}
)
.then(res => console.log(res.data))
.catch(err => console.log(err));
}
and here is my get request method that receives the data coming from vuejs
router.get('/result/', (req, res) => {
const sql = "SELECT * FROM \
ND_HOSP WHERE hosp_ptype = 'h' AND hosp_name LIKE ?";
console.log(req.body)
myDB.query(sql, ['%' + req.body.hosp_name + '%'], (err, result) => {
if (err) {
console.log(err)
} else {
try {
res.send(result);
/* console.log(result) */
} catch (err) {
res.send(err)
}
}
})
})
but it gives me error and says
http://localhost:9002/hospita/result/[object%20Object]
In your getResult() let's to change method to post and + to , for passing your data in body. You can look at this code below:
getResult() {
axios
.post( // <= change method to post
`${process.env.VUE_APP_API}/hospita/result/`, // change `+` with `,`
{
hosp_name: "SAMPLE"
}
)
.then(res => console.log(res.data))
.catch(err => console.log(err));
}
After that, don't forget to change your router method from get to post. You can look at this code below:
// change method `get` to `post`
router.post('/result/', (req, res) => {
const sql = "SELECT * FROM \
ND_HOSP WHERE hosp_ptype = 'h' AND hosp_name LIKE ?";
console.log(req.body)
myDB.query(sql, ['%' + req.body.hosp_name + '%'], (err, result) => {
if (err) {
res.send(err)
} else {
res.send(result);
}
})
})
Make sure, because we're use the req.body, so, don't forget to add body parser in your server.js or app.js. It's will looks like this code below:
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
I hope it can help you.
problem is here: res.send(result);
result is not containing json data, it is containing any other object or blank object like {}.
so first of all try to see what is inside the result using console.log().
most of the time for the such cases two functions are very useful.
JSON.stringify(object);
JSON.parse(strobj);

How can I pass parameters to a generic Express middleware function on a per-route basis?

I've written a generic validation function that takes an object as the first parameter with a property required (amongst others) that is an array of strings. It is used to test incoming front end data against any keys found in required. Here's a mockup of how it looks - using underscore to check isEmpty:
validator.js
function validateRequest(options, req, done) {
const { body } = req;
const { required } = options;
const errors = {};
if (required && required.length) {
required.forEach(key => {
const value = `${body[key]}`;
if (_.isEmpty(value)) {
errors[key] = 'This field is required';
}
});
}
if (_.isEmpty(errors)) done({ success: true });
else done({ success: false, errors });
}
And I currently use it as such:
routes/auth.js
router.post('/login', (req, res, next) => {
validator.validateRequest({
required: ['identifier', 'password']
}, req, result => {
if (!result.success) return res.status(400).send(result);
next();
});
}, auth.login);
I'd like to be able to use it in a cleaner fashion, something more like:
router.post('/login', validator.validateRequest({
required: ['identifier', 'password']
}), auth.login);
I'd rather not use app.use because it involves me having to worry about always positioning it in the proper hierarchy of the route files and is not scalable in the sense I need it to be. I also have other parameters being passed as options, validating input as numeric, correct zip codes, etc. so the function above is much more in-depth than the example. Is there a way for me to use something like this on a route-by-route basis? There's gotta be a way for my middleware functions to intercept (req, res, next) without having to write it out each time.. right?
The answer was to use a higher order function returning a function that takes (req, res, next) as parameters like this:
validator.js
function validateRequest(options) {
return (req, res, next) => {
const { body } = req;
const { required } = options;
const errors = {};
if (required && required.length) {
required.forEach(key => {
const value = `${body[key]}`;
if (_.isEmpty(value)) {
errors[key] = 'This field is required';
}
});
}
if (!_.isEmpty(errors)) {
res.send({ success: false, errors });
} else {
next();
}
}
}
then it can be used like so:
routes/auth.js
router.post('/login', validateRequest({
required: ['identifier', 'password']
}), auth.login);

Extend express res.json function

I was wondering how to extend the res.json function. I want it to do some string replacements before doing its normal duty.
My idea is to use that for translations
{
value:'some key'
}
and it comes out
{
value:'translated text'
}
any idea how to do that.
You could define a middleware that will replace res.json() with a function of your own:
app.use((req, res, next) => {
let json = res.json.bind(res);
res.json = (data) => {
let newData = ...perform replacements here...
return json(newData); // call the original `res.json()`, stored as `json`
};
next();
});

Categories

Resources