I am trying to get a simple server-side cache up-and-running within node/express with a GET request. Managed to get this working when dealing with simple URL parameters, but not sure how to approach it with a JSON body.
This is what I have thus far for the URL parameter version:
const mcache = require('memory-cache');
app.set('view engine', 'jade');
const cache = (duration) => {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url;
let cachedBody = mcache.get(key);
if (cachedBody) {
res.send(cachedBody);
return;
} else {
res.sendResponse = res.send;
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body);
};
next();
}
};
};
app.get('/user/:id', cache(10), (req, res) => {
setTimeout(() => {
if (req.params.id == 1) {
res.json({ id: 1, name: 'John' });
} else if (req.params.id == 2) {
res.json({ id: 2, name: 'Bob' });
} else if (req.params.id == 3) {
res.json({ id: 3, name: 'Stuart' });
}
}, 3000); //setTimeout was used to simulate a slow processing request
});
Any pointers?
For sure in your cache middleware you shouldn't be using res.send because you can only perform one response per request. So before you get to res.json in your app.get, res is already send.
I assume that you want to pass the data with that res.send to the app.get? If so you can either pass it inside next() or use res.locals like res.locals.varName = catchedBody and then you can use that inside your app.get function because it will be available during entire http request
Related
I created a simple rest-api app(nodeJS+ExpressJS+MySQL) as a test project.
It's functioning well, so I wanted to add HTML(EJS) to display the data so I have been working on displaying views.
However, I wasn't able to pass on MySQL Data to HTML.
** As I'm still learning nodeJS, I'm still confused as some tutorials added routers in app.js/server.js
Since I have created getAll() in the controller, I used Restaurant.getAll() in server.js.
Below is the console.log result of Restaurant.getAll(). I tried to used JSON.stringtify(data) already, but there's no difference.
I would appreciate if anyone could let me how I could change or fix my codes.
Appreciate all your helps.
restaurant_list: [
RowDataPacket {
id: 1,
name: 'A',
genre: 'Japanese',
rating: 4
},
RowDataPacket {
id: 2,
name: 'B',
genre: 'Mexican',
rating: 4
},
RowDataPacket {
id: 3,
name: 'C',
genre: 'Chinese',
rating: 4
}
]
server.js
const express = require("express");
const Restaurant = require("./app/models/restaurant.model.js");
const app = express();
app.use(express.json()); //Used to parse JSON bodies
app.use(express.urlencoded()); //Parse URL-encoded bodies
// set the view engine to ejs
app.set('views', './app/views');
app.set('view engine', 'ejs');
// simple route
app.get("/", function(req, res) {
var data = Restaurant.getAll();
res.render("index.ejs", { data: data });
});
require("./app/routes/restaurant.routes.js")(app);
// set port, listen for requests
app.listen(3000, () => {
console.log("Server is running on port 3000.");
});
restaurant.model.js
const sql = require("./db.js");
// constructor
const Restaurant = function(restaurant) {
this.name = restaurant.name;
this.genre = restaurant.genre;
this.rating = restaurant.rating;
};
Restaurant.getAll = result => {
sql.query("SELECT * FROM restaurant_list", (err, res) => {
if (err) {
console.log("error: ", err);
result(null, err);
return;
}
console.log("restaurant_list: ", res);
result(null, res);
});
};
module.exports = Restaurant;
restaurant.routes.js
module.exports = app => {
const restaurant_list = require("../controllers/restaurant.controller.js");
// Retrieve all restaurants
app.get("/", restaurant_list.findAll);
};
Regarding your orginal question, it seems that the response you got is accessible as a normal object (ref: How to access a RowDataPacket object) and that RowDataPacket is just the constructor name of the object response.
Thus you are not using the callback return of the function getAll. You are passing it a callback named result and returning the standard (err, result). But you are treating it as normal synchronous function. Change this:
// simple route
app.get("/", function(req, res) {
var data = Restaurant.getAll();
res.render("index.ejs", { data: data });
});
To this:
app.get("/", function(req, res) {
Restaurant.getAll(function(err, data) {
if (err) // handle your error case
else {
res.render("index.js", { data: data });
}
});
});
And even better (es6 syntax):
app.get("/", function(req, res) {
Restaurant.getAll((err, data) => {
if (err) // handle your error case
else res.render("index.js", { data });
});
});
Using arrow functions and object destructuring.
Be careful because in the err callback of your getAll function you are returning (null, err) and you should return result(err) as the second parameter is not needed since you should always check for err first.
Also, as an advice, i would like to suggest you to use standard javascript Classes instead of doing the same thing on a normal constant object. Also, you could get rid of the callbacks in your controller functions and treat your query as asynchronous calls like this.
I'm currently learning about creating a server using the node.js framework Express.
Using the code below I understand how to access or GET the list of students or GET 1 student by name, however I'm not sure how to DELETE a student by id. I tried the localhost:4000/students/1 and it doesn't work, what should the URL it look like?
Same questions for the app.put method. If I type localhost:4000/students/James/art/50 I simply get an error saying "cannot get localhost:4000/students/James/art/50" so it never actually uses the put method.
const express = require('express');
const bodyParser = require('body-parser');
app.use(bodyParser.json());
let students = [
{
id: 1,
name: 'James',
classes: {
computer: 95,
art: 92
}
},
{
id: 2,
name: 'Leeroy',
classes: {
computer: 95,
art: 92
}
}
];
app.get('/students', (req, res) => {
res.json(students);
});
app.get('/students/:name', (req, res) => {
res.json(students.find((student) => student.name === req.params.name));
});
app.put('/students/:name/:class/:grade', (req, res) => {
const student = students.find((student) => student.name === req.params.name);
if (student) {
student.classes[req.params.class] = parseInt(req.params.grade);
res.status(201).send(`Student ${req.params.name} was assigned a grade of ${req.params.grade} in ${req.params.class}`);
} else {
res.status(404).send('Student not foun');
}
});
app.delete('/students/:id', (req, res) => {
const student = students.find((student) => student.id === req.params.id);
if (student) {
students = students.filter((obj) => obj.id !== req.params.id);
res.status(201).send('Student was deleted.');
}
});
app.listen(4000, () => {
console.log('Your app is listening on port 4000');
});
You'll need to use a tool in order to test requests different to GET, I recommend Postman there you'll be able to test the DELETE, PUT and POST requests
Before I call expressApp.use(express.static(path.join(__dirname, '/../frontend/dist'))); I need to modify the html-code. What I basically need to do is inserting meta tags in two middleware functions. I figured out how to do this. But with my solution I call a middleware-functions inside another one.
app.js
let frontend = await fs
.readFileSync(path.join(__dirname, '/../frontend/dist/index.html'))
.toString('utf8');
expressApp.use((req, res, next) => {
//...
frontend = frontend.replace(
'<meta device="ABC" />',
'<head><meta device="' + deviceId + '"/>'
);
next();
});
expressApp.use((req, res, next) => {
const language = req.get('language') || 'en_GB';
logger.info('language:' + language);
this._languageModule.setLanguage(language);
frontend = this._languageModule.insertSIDs(frontend);
logger.info(frontend);
expressApp.use(express.static(path.join(__dirname, '/../frontend/dist'))); // nested middleware function
next();
});
/** set up all the express routes **/
expressApp.get('/', (req, res) => {
res.send(frontend);
});
Edit
If I don't call expressApp.use(express.static(path.join(__dirname, '/../frontend/dist'))); nested - like this:
expressApp.use((req, res, next) => {
const language = req.get('language') || 'en_GB';
logger.info('language:' + language);
this._languageModule.setLanguage(language);
frontend = this._languageModule.insertSIDs(frontend);
logger.info(frontend);
next();
});
expressApp.use(express.static(path.join(__dirname, '/../frontend/dist')));
the HTML will not be served modified.
You probably should write your own middleware that handles the modification of the files. Here's an example not tested. But it's rough. It's based on the express.static function
const fs = require("fs");
var parseUrl = require('parseurl')
app.use((req, res, next) => {
var originalUrl = parseUrl.original(req)
var path = parseUrl(req).pathname
// make sure redirect occurs at mount
if (path === '/' && originalUrl.pathname.substr(-1) !== '/') {
path = ''
}
// We only answer to GET
if (req.method !== 'GET' && req.method !== 'HEAD') {
return next()
}
let path = path;
fs.exists(path, (exists) => {
if(!exists)
{
// file don't exists skip this middleware
return next();
}
fs.readFile(path, (err, data) => {
if (err)
{
// Can't send the file skip this middle ware
return next();
}
// Do whatever you need with the file here?
// ...
// Setup mime type of the file
res.setHeader("content-type", "text/html");
// send the client the modified html
res.send(data);
});
console.log(exists ? 'it\'s there' : 'no passwd!');
});
});
For the original source please take a look at this github page:
https://github.com/expressjs/serve-static/blob/master/index.js
I am currently trying to create a client-side reroute for users that are invalid. The server validates if the user has access to the current page and if not it returns {data: 'invalid'} in the success callback of the ajax call I check this value with the following:
if (data.status === 'invalid') {
window.location.href = domain + '/login';
return false;
}
This works sometimes but other times I receive the following browser alert message:
RequestAbortedError: Request aborted
I have attempted to swap out window.location.href with window.location.replace() and top.location.href but neither resolved the issue. I can see that the server is processing the information correctly and returning {data: 'invalid'} but as soon as it tries to run the line window.location.href I receive this error. I have an image below if it helps.
When "OK" is clicked the page does redirect to the appropriate page. The end result is happening as expected but I cannot resolve the error.
UPDATE INCLUDING SERVER SIDE CODE
function authentication (req, res, next) {
console.log('entered');
if (typeof req.rsaConPortal.email !== 'undefined') { // Check if session exists
console.log('passed 1');
User.findOne({ "user.email": req.rsaConPortal.email, "user.status”: req.resConPortal.status}, function (err, user) {
if (!user) {
console.log('failed 2');
req.rsaConPortal.reset();
res.send({status: 'invalid'});
} else {
console.log('passed 2');
req.rsaConPortal.email = user.user.email;
req.rsaConPortal.id = user._id;
req.rsaConPortal.status = user.user.status;
next();
}
});
} else {
console.log('failed 1');
res.send({status: 'invalid'});
}
}
app.get('/api/savedApp/', authentication, function(req, res) {
if (req.rsaConPortal.status !== 'registered') {
res.send({status: 'invalid'});
} else {
User.find({ "_id": req.rsaConPortal.id }, {
profile: 1, documents: 1 }, function(err, user) {
if (err) throw err;
res.send(user);
});
}
});
Is there a better way to authenticate my users? I am using Mozilla's Client-Sessions npm package
The logs on the server are logging "Passed1" and "Passed2". It is sending the client "Invalid" based off the status inside the get call.
Based on reading further about express and a few comments I have received on this question I have decided to re-think my approach and look for a better alternative which I am happy to say I have found in express.Router. I was able to create an authentication function to determine if the user is authorized and handle the business logic of whether to let the user pass or send them back to the login. Then I created a route for each page that I have that takes the business logic a step further based on the users status and either lets them pass or sends them back to login.
Thanks to everyone who looked into this and provided comments.
var router = express.Router();
app.use(router);
var authenticate = function(req, res, next) {
if (req.rsaConPortal !== undefined) {
if (req.rsaConPortal.email !== undefined) { // Check if session exists
// lookup the user in the DB by pulling their email from the session
User.findOne({ "user.email": req.rsaConPortal.email, "user.password": req.rsaConPortal.passport }, function (err, user) {
if (!user) {
// if the user isn't found in the DB, reset the session info and
// redirect the user to the login page
req.rsaConPortal.reset();
req.rsaConPortal.email = '';
req.rsaConPortal.passport = '';
req.rsaConPortal.id = '';
req.rsaConPortal.status = '';
res.redirect('../login');
} else {
req.rsaConPortal.email = user.user.email;
req.rsaConPortal.passport = user.user.password;
req.rsaConPortal.id = user._id + '';
req.rsaConPortal.status = user.user.status;
next();
}
});
} else {
res.redirect('../login');
}
} else {
res.redirect('../login');
}
};
router.get(['/','/create_warranty','/help','/marketing_assets','my_documents','profile'], authenticate, function(req, res, next) {
if (req.rsaConPortal.status !== 'approved') {
res.redirect('../login');
} else {
next();
}
});
I have a form on client-side which is sending data to server through AJAX in my Express app. I want to show some responses to the user when there are errors or the message sent successfully.
This code represents an error message when the message cannot be sent. The specific div in my handlebars template looks like this:
<div class="form-validation-error" style="{{formValidationError}}">ERROR: Message cannot be sent!</div>
Which is turned off by CSS default:
.form-validation-error {
display: none;
}
In my routes/contact.js I have a router.post block which is handling the message sending:
router.post('/', (req, res, next) => {
if(req.body.firstname.length === 0 || !req.body.firstname.match(/\D+/igm)) {
var validateFirstname = false;
} else {
var validateFirstname = true;
};
if(req.body.captcha.length === 0 || !req.body.captcha.match(/^kettő|ketto|two$/igm)) {
var validateCaptcha = false;
} else {
var validateCaptcha = true;
};
if(validateFirstname === true && validateCaptcha === true) {
console.log('SUCCESS: Form validated! The Nodemailer script will be here!');
} else {
console.log('ERROR: Form not validated!');
const formValidationErrorTrue = 'display: block;'; // -> How can I achieve this!??
res.send({formValidationError: 'display: block;'}); // -> Or this!??
};
});
After this block, I have a router.get template rendering part:
router.get('/', (req, res, next) => {
fsAsync((err, data) => {
if(err) {
res.render('404', {
title: 'Error 404'
});
}
const contact = data[2].contact;
res.render('contact', {
title: 'Contact',
data: contact,
formValidationError: formValidationErrorTrue // -> How can I achieve this!??
});
});
});
My question is, how can I share variables between router.post and router.get?
You could create a method and call that one in both get and post routes. I would encapsulate all logic in a controller instead of directly in your route. Perhaps you could also solve this using middleware (google express middleware) but I usually see that being used for authentication or error logging.
(Sorry for the short answer. I am typing on my phone)