How to handle express routing where more than one route matches? - node.js

I have an express app set up using http-proxy-middleware, but I'm having a problem routing only a subset of requests through the proxy.
Here is what my config looks like:
app.use(/\/.*-img/i, proxy({changeOrigin: true, logLevel: 'debug', target: 'http://ucassets.blob.core.windows.net'}));
app.get('*', (req, res) => {
const location = req.url;
const memoryHistory = createMemoryHistory(req.originalUrl);
const store = configureStore(memoryHistory);
const history = syncHistoryWithStore(memoryHistory, store);
match({history, routes, location},
(error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
/* render HTML and send code 200 */
} else {
res.status(404).send('Not Found?');
}
});
});
The proxy route sometimes works, and other times ends up printing that "Not found?" 404 result. Of note, there is NOT a route in my react-router config that matches the proxy route above. Since both routes technically match the express route, I guess express is randomly choosing which one to execute?
EDIT: Here's an example route that is being problematic:
/user-img/bc070850-11c9-a79e-2881-9bd6cc04c3ca.jpg

Related

Cannot run .JSON on response from the Fetch API

I am working with React, Express, PostgreSQL, Node, and the Fetch API. When I try to run a "GET" request (within a try block) to get data from my database, the request fails (and enters the catch block) with the following error:
Unexpected token < in JSON at position 0
Here is the failing code that I have on the front end:
const getRequests = async () => {
try {
const responseInfo = await fetch("/api/advice-requests", {
headers: { "Accept": "application/json" },
});
if (responseInfo.status === 200) {
console.log("200 running"); // This is logged to the console.
const data = await responseInfo.json();
console.log("data :", data); // This is NOT logged to the console. It fails.
setAdviceRequests(data.requests);
setResponses(data.responses);
return;
}
} catch (error_o) {
// The UI is updated with the text of the below error
setError(
"Something went wrong on our end. We are looking into how we can improve..."
);
return;
}
};
Here is some of my server code (there is more, but it is not relevant), including some changes I made that worked to solve this problem for other people.
const adviceRequests = require("./controllers/adviceRequests");
const express = require("express");
const app = express();
const cors = require("cors");
app.use(express.json());
app.use(cors());
app.options("*", cors());
if (process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, "../build")));
app.get("/*", (req, res) => {
res.sendFile(path.join(__dirname, "../build", "index.html"));
});
}
app.get("/api/advice-requests", adviceRequests.getAll);
app.listen(PORT, () => {
console.log(`SERVER RUNNING ON ${PORT}.`);
});
Lastly, here is the adviceRequests.getAll function:
getAll: async (req, res) => {
const db = req.app.get("db");
try {
let requests = await db.requests.getAll(req.session.user.id);
let responses = await db.responses.getAll([req.session.user.id]);
return res.status(200).send([requests, responses]);
} catch (error) {
return res.status(500).send({
message:
"Something went wrong on our end. We are looking into how we can improve.",
error,
});
}
},
A bit more information:
The code works just fine when I run it locally
Even on the live server, I can successfully run several POST requests for authentication and adding requests. I just cannot get them.
I have done quite a bit of research into this and am posting my own as a last resort. Nothing that has worked for other people has worked for me so far.
Everytime I have had this "Unexpected token < in JSON at position 0" it was because i was trying to parse an html plain response as if it was a json. Note that every html file starts with a <.
I suggest you change this console.log("200 running"); with a console.log(responseInfo);, that way you'll notice if the response is a json or not.
From what I see, the problem might be the order in which the app.get are defined. Note that express serves first come first served, so since you have already defined an app.get("/*"), everything will be served by that route. Also note that you are sending back an index.html, which matches the issue shown in the frontend.

express.static() and sendFile() problem... creating a dynamic host with nodejs

After configure my web server with nginx, i redirected all *.example.com to my nodejs server.
But before, i handle the http request, i check the url and host to see if it is correct or not.
For example, if the user writes something like what.ever.example.com
I redirect him to the main website because that host is not valid.
otherwise if the user writes something like mydomain.example.com
The user should access to this website and receive the angular APP.
So i am doing something like this.
UPDATED CODE
const express = require('express');
const cors = require('cors');
const mongoose = require('./server/database');
const bodyParser = require('body-parser');
const app = express();
var path = require('path');
// Settings
app.set('port', process.env.PORT || 4000)
// Middlewares
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.json());
app.use(cors());
// Routes API
app.use('/api/users', require('./server/routes/usuarios.routes'));
app.use('/api/almacenes', require('./server/routes/almacen.routes'))
app.use('/api/updates', require('./server/routes/update.routes'))
app.use('/api/dominios', require('./server/routes/dominios.routes'))
app.get('/', checkHost);
app.get('/', express.static('../nginx/app'));
app.get('/*', checkPath);
function checkHost(req, res, next) { //With this function what i pretend is check the subdomain that the user send, and if it doesn't exist. redirect it.
var domain = req.headers.host
subDomain = domain.split('.')
if (subDomain.length == 3) {
subDomain = subDomain[0].split("-").join(" ");
let query = { dominio: subDomain }
var dominiosModel = mongoose.model('dominios');
dominiosModel.findOne(query).exec((err, response) => {
if (response != null) {
if (response.dominio == subDomain) {
next();
} else {
res.writeHead(303, {
location: 'http://www.example.com/index.html'
})
res.end()
}
} else {
res.writeHead(303, {
location: 'http://www.example.com/index.html'
})
res.end()
}
})
} else {
res.writeHead(303, {
location: 'http://www.example.com/index.html'
})
res.end()
}
}
function checkPath(req, res, next) { //With this function what i want to do is.. if the user send *.example.com/whatever, i redirect it to *.example.com
if (req.url !== '/') {
res.writeHead(303, {
location: `http://${req.headers.host}`
})
res.end()
} else {
next()
}
}
// Starting Server.
app.listen(app.get('port'), () => {
console.log('Server listening on port', app.get('port'));
});
All redirects are working well, but when in checkHost the subDomain matched, it doesnt send nothing to the front... so what can i do here?
Try removing the response.end(). Since .sendFile() accepts a callback, it is most likely an async function, which means that calling .end() right after .sendFile() will most probably result in a blank response.
The sendFile function requires absolute path of the file to be sent, if root is not provided. If root is provided, a relative path could be used, but the root itself should be absolute. Check documentation here: https://expressjs.com/en/api.html#res.sendFile
You should try to send your index.html in following manner:
app.get('*', checkPath, checkHost, function (req, response) {
response.sendFile('index.html', { root: path.join(__dirname, '../nginx/app') });
}
This should work provided that the path ../nginx/app/index.html is valid, relative to the file in which this code is written.
Additionally, based on the sample code (and the comments), you probably don't need the express.static(...) at all. Unless, you need to serve 'other' files statically.
If it is needed, then the app.use(express.static('../nginx/app')) should be outside the controller. It should probably be added before the bodyParser, but since you are concerned about someone being able to access 'index.html' via the static middleware, you can consider following order for your middlewares:
//existing body parser and cors middlewares
// existing /api/* api middlewares.
app.use(checkPath);
app.use(checkHost);
app.use(express.static('../nginx/app'));
If the checkPath middleware is modified slightly to redirect to /index.html, the middleware with '*' path might not be required at all with this setup.

Serving VueJS Builds via Express.js using history mode

I want to serve vue js dist/ via express js. I am using history router in vue js app.
The following are the api calls
api/
s-file/sending/:id
terms/get/:which
As i have figured out a solution in python here. I don't know how to do it in node js with express
The code i am using right now is
app.use(function (req, res, next) {
if (/api/.test(req.url))
next();
else {
var file = "";
if (req.url.endsWith(".js")) {
file = path.resolve(path.join(distPath, req.url))
res.header("Content-Type", "application/javascript; charset=utf-8");
res.status(200);
res.send(fs.readFileSync(file).toString());
} else if (req.url.endsWith(".css")) {
file = path.resolve(path.join(distPath, req.url))
res.header("Content-Type", "text/css; charset=utf-8");
res.status(200);
res.send(fs.readFileSync(file).toString());
} else {
file = path.resolve(path.join(distPath, "index.html"))
res.header("Content-Type", "text/html; charset=utf-8");
res.status(200);
res.send(fs.readFileSync(file).toString());
}
}
})
Have a look at connect-history-api-fallback that is referenced in the vue docs.
This should solve your problems.
Example using connect-history-api-fallback
var express = require('express');
var history = require('connect-history-api-fallback');
var app = express();
// Middleware for serving '/dist' directory
const staticFileMiddleware = express.static('dist');
// 1st call for unredirected requests
app.use(staticFileMiddleware);
// Support history api
// this is the HTTP request path not the path on disk
app.use(history({
index: '/index.html'
}));
// 2nd call for redirected requests
app.use(staticFileMiddleware);
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
The very simpler one if anyone wants to use
Just add this below all the valid routes and above app.listen
app.all("*", (_req, res) => {
try {
res.sendFile('/absolute/path/to/index.html');
} catch (error) {
res.json({ success: false, message: "Something went wrong" });
}
});
Make sure you have included
app.use(express.static('/path/to/dist/directory'));

Nodejs Express - return 405 for un-supported method

I'm running a standard NodeJs 8 with Express and currently when a request for an existing path but un-supported method comes in, Express return 404.
For example 'POST /login' is supported, but 'GET /login' is not, but it returns 404.
How can I make Express return 405 in such a case?
Here's the routes file:
const express = require('express');
const router = express.Router();
const loginController = require('../controllers/login');
router.route('/login').post(loginController.loginUser);
module.exports = router;
Please advise.
You can simply add the .all() handler to your route chain, like so:
const methodNotAllowed = (req, res, next) => res.status(405).send();
router
.route(`/login`)
.post(loginController.loginUser)
.all(methodNotAllowed);
Explanation
This works because requests are passed to the handlers in the order they are attached to the route (the request "waterfall"). The .post() handler will catch your POST requests, and the rest will fall through to the .all() handler.
Also see this question for more details.
Authenticating all POST routes
If you would like to ensure that the user is logged in for all POST requests, but return a 405 response for any other requests, you can use a regular expression to match all routes with router.post('*'), like so:
router
.post(`*`, loginController.loginUser)
.all(methodNotAllowed);
The problem with this approach, however, is that no 404 errors will ever be returned to the client, only 405. Therefore I recommend attaching the methodNotAllowed handler to each individual route, like in the first code snippet above. This approach will return 404 errors for routes that don't exist, but 405 errors for routes that do.
Determining the available methods for a route
To determine which methods are allowed for a route, use router.stack:
app.use((req, res, next) => {
const methods = router.stack
// Filter for the route that matches the currently matched route
.filter(layer => layer.route.path === req.path)[0]
.route
.methods;
if (!methods[req.method]) methodNotAllowed(req, res, next);
else next();
});
You can try this that way:
app.route("/login")
.get((req, res) => {
/* HANDLE GET */
})
.post((req, res) => {
/* HANDLE POST */
})
.all((req, res) => {
res.status(405).send();
});
How it works?
If request matches the route. It will go through the handlers. If a handler is present, it will be handled using that specific one. Otherwise, it will reach the 'all' handler that will set the status code to 405 and send the response.
Here You can find the discussion about it:
405 issue
#You question below:
You can try that way:
loginRoutes.js content:
const router = require('express').Router();
router.route('/')
.get((req, res) => {
res.status(200).send()
})
module.exports = router
server file content:
const express = require('express')
const app = express();
const router = express.Router();
const loginRoutes = require('./loginRoutes')
const PORT = process.env.PORT || 8080;
router.use('/login', loginRoutes)
router.route('/login').all((req, res) => { res.status(405).send() })
app.use(router);
app.listen(PORT, () => console.log(`started on port: ${PORT}`))
You can use this snippet of code to automatically send 405 status code when route from the same path exist but not with the current method
app.use(function (req, res, next) {
const AllLayers = app._router.stack
const Layers = AllLayers.filter(x => x.name === 'bound dispatch' && x.regexp.test(req.path))
const Methods = [];
Layers.forEach(layer => {
for (let method in layer.route.methods) {
if (layer.route.methods[method] === true) {
Methods.push(method.toUpperCase());
}
}
})
if (Layers.length !== 0 && !Methods.includes(req.method)) {
res.setHeader('Allow', Methods.join(','))
if (req.method === "OPTIONS") {
return res.send(Methods.join(', '))
}
else {
return res.sendStatus(405);
}
}
else {
next();
}
});
Hope this could be helpfull to someone
If you want to determine what methods COULD have been used you need to do a lot of digging in the app function you start your server with, and through some string manipulation and the like you can figure out what the possible methods are and return them in the error. If you're interested in how its done check out https://github.com/Justinlkirk/express-ez-405 or just use the npm package here https://www.npmjs.com/package/express-ez-405

How to have express server make an initial http request before serving static files

I have a basic express server being used for an api server and serving static files for a react app like so:
app.use(express.static('/public'))
app.use('/api', apiRouter)
When a user initially loads the app, I need to retrieve data from a different server that I'll need for the api server before serving up the static files. Is there a way to have express make an initial http request to another server and cache the results on the server before serving up the static files?
Ideally, I want the server to make this request only once (when the user initially loads the app). I've looked into using express middleware, but am having trouble figuring out where to insert the middleware so that it only gets called when the files are served up.
First off, if you really want to only make a request once to some outside server, then you should probably just do it upon server startup. There's really no reason to wait until some request comes in if you're going to cache the result anyway and it doesn't matter what route or what user is making the first request.
But, if you want to trigger it only when a request comes in, then you can use middleware to do so. An outline of the middleware could look something like this:
let cachedResult;
app.use((req, res, next) => {
if (cachedResult) {
next();
} else {
request(..., (err, response, body) => {
if (err) {
next(err);
} else {
cachedResult = ....
next();
}
});
}
});
If you want the middleware to ONLY execute when a static resource is requested, then you need some way of determining which incoming requests are for static resources and which are not.
The code you show:
app.use(express.static('/public'))
checks the public sub-directory for a match for any request which isn't very helpful. If you would prefix all static resources with a prefix such as /static, then you could target only your static files like this:
app.use('/static', express.static('/public'));
And, you could run the middleware only for the static files like this:
app.use('/static', (req, res, next) => { ... });
app.use('/static', express.static('/public'));
Request. Request is designed to be the simplest way possible to make http calls. It supports HTTPS and follows redirects by default.
A simple request example could look like:
var request = require('request');
request({
url: "https://some.url.com",
method: "POST",
headers: { "content-type" : "application/json" },
json: payload
},
function (error, response, data) {
if (!error && response.statusCode == 200) {
//handle request
}
else {
callback({ "errorMessage": "There was an error with the request" });
}
}
);
Ok, first: caching. Define a variable or a module that will be accessible to your future middleware:
./cache.js
const cache = {};
module.exports = cache;
This module will hold users' data, like:
{
'id00001': {
email: 'foo#bar.com',
birthday: 1488700668567
},
'id00002': {
email: 'baz#bar.com',
birthday: 1488700668590
},
// etc...
}
Next, use Request and Express middleware:
./app.js
const express = require('express'),
app = express();
const cache = require('./cache'),
request = require('request');
const getUserDataMiddleware = (req, res, next) => {
// assuming you have a mechanism to identify your users already somehow
let user = req.user,
id = user.id;
// if data is not in cache
if (!cache[id]) {
request.get({
url: 'http://getuserdata.com/' + id,
json: true
}, (err, response, body) => {
if (err) {
next(err);
return;
}
// save received data to cache
cache[id] = body;
next();
});
} else {
// you have your user data in cache, do whatever you want now
next();
}
};
app.use('/public', getUserDataMiddleware, express.static('./public'));

Resources