Extend express res.json function - javascript

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();
});

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.

how to break logic into a controller and a model in a node

I do not quite understand how to properly break the logic on the controllers and models in nodeJS when working with the backend application. Suppose I have an example
This code is in the model of my application, and logically I understand that the model is only responsible for choosing from the database, and the controller and everything else should be done by the controller, but I don’t quite understand how to do this and I tried to transfer part of the code to the controller and export it, but I did not succeed (Please, help, at least with this example! The main thing for me is to understand the principle of working with MVC in the node !!!
exports.currentPostPage = function(req, res){
db.query('SELECT * FROM `posts`', function (err, result) {
if (err){
console.log(err);
}
var post = result.filter(item => {return (item.id == req.params.id)? item: false})[0];
if (post === undefined){
res.render('pages/404');
} else {
res.render('pages/post-page', {postId: req.params.id, item: post});
}
});
};
So, you're on the right track. There's a lot of different ways to do it depending on preferences, but one pattern I've seen pretty commonly is to use the callback as a way to integrate. For example, let's say you have your model file:
exports.getPostById = (id, cb) => {
db.query('SELECT * FROM `posts` WHERE id=?', [id], function (err, result) {
if (err){
return cb(err); // or, alternatively, wrap this error in a custom error
}
// here, your logic is just returning whatever was returned
return cb(null, result);
});
};
Note I also am letting the DB handling the ID lookup, as it's probably more efficient at doing so for larger data sets. You didn't say what DB module you're using, but all the good ones have some way of doing parametrized queries, so use whatever works w/ your DB driver.
Anyway, the Model file therefore handles just the data interaction, the controller then handles the web interaction:
// postController.js
const model = require('../models/postModel.js'); // or whatever you named it
exports.populatePost = (req, res, next, id) => {
model.getPostById(id, (err, post) => {
if (err) return next(err); // centralized error handler
req.post = post;
next();
});
}
export.getOnePost = (req, res, next) => {
if (req.post) {
return res.render('pages/post-page', req.post);
}
// again, central error handling
return next({ status: 404, message: 'Post not found' });
}
I have mentioned central error handling; I vastly prefer it to scattering error handling logic all over the place. So I either make custom errors to represent stuff, or just do like above where I attach the status and message to an anonymous object. Either will work for our purposes. Then, in a middleware file you can have one or more handler, the simplest like this:
// middleware/errors.js
module.exports = (err, req, res, next) => {
console.error(err); // log it
if (err.status) {
return res.status(err.status).render(`errors/${err.status}`, err.message);
}
return res.status(500).render('errors/500', err.message);
}
Finally, in your routing setup you can do things like this:
const postController = require('../controllers/postController');
const errorHandler = require('../middleware/errors.js');
const postRouter = express.Router();
postRouter.param('postId', postController.populatePost);
postRouter.get('/:postId', postController.getOnePost);
// other methods and routes
app.use('/posts', postRouter)
// later
app.use(errorHandler);
As was pointed out in the comments, some folks prefer using the Promise syntax to callbacks. I don't personally find them that much cleaner, unless you also use the async/await syntax. As an example, if your db library supports promises, you can change the model code to look like so:
exports.getPostById = async (id, cb) => {
// again, this assumes db.query returns a Promise
return await db.query('SELECT * FROM `posts` WHERE id=?', [id]);
}
Then your controller code would likewise need to change to handle that as well:
// postController.js
const model = require('../models/postModel.js'); // or whatever you named it
exports.populatePost = async (req, res, next, id) => {
try {
const post = await model.getPostById(id)
req.post = post
return next()
} catch (err) {
return next(err)
}
}

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);

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

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);
});

Categories

Resources