Using an ExpressJS router with subdomains - node.js

Due to my shared-hosting situation, I was forced to have all incoming requests redirected to a non-80 port on localhost, which I did through htaccess. This mutates the HTTP host header to always say localhost, no matter what subdomain I try to request. Simple enough, I found a workaround to use x-forwarded-host instead, but this means that I'm not able to rely on any of the Express subdomain middleware packages currently available on NPM, as they all rely on that host header (as far as I'm aware).
So I managed to send requests to the router. But for some reason, any request handling I try to do with the router for the specific subdomain that I have activated, won't get picked up by the router, and passes straight on through to the 404 handler.
/index.js:
const path = require('path');
const fs = require('fs');
const express = require('express');
const app = express();
const PORT = 30001;
const subdomainHosts = [];
fs.readdirSync('./routes/subdomain').filter(file => file.endsWith('.js')).forEach(host => {
subdomainHosts.push(host.split('.js')[0]);
});
const forwardSubdomain = require('./middlewares/forwardSubdomain');
app.use(forwardSubdomain(subdomainHosts));
app.use(express.static(path.join(__dirname, 'public')));
app.use((req, res, next) => {
return res.status(404).sendFile(path.join(process.cwd(), 'public/404.html'));
});
/middlewares/forwardSubdomain.js:
module.exports = (subdomainHosts) => {
return (req, res, next) => {
let host = req.headers['x-forwarded-host'] ? req.headers['x-forwarded-host'] : '';
host = host.split(':')[0].split('.example.com')[0];
const isSubdomain = (host && subdomainHosts.includes(host));
if (isSubdomain) {
const subdomainRouter = require(`../routes/subdomain/${host}.js`);
return subdomainRouter(req, res, next);
}
next();
};
};
/routes/subdomain/test.js:
const path = require('path');
const express = require('express');
const router = express.Router();
router.use(express.static(path.join(process.cwd(), 'test')));
module.exports = router;
/test/ contains a simple hello world index.html. Yet, trying to access test.example.com gives me the 404.
I'm pretty certain that I'm missing something obvious, but I've been sitting on this for a bit too long, hence the cry for help.

Related

node and express error "cannot GET /" even after I included app.get() in my server.js

I am trying to start my project via launching server.js but I am getting error:"cannot GET /"
even after I made an app.get() route in my server.js
I am using also "body-parser" as a middleware, and "cors"
server.js:
// Setup empty JS object to act as endpoint for all routes
const projectData = {};
// Require Express to run server and routes
const express = require('express');
// Start up an instance of app
const app = express();
/* Middleware*/
//Here we are configuring express to use body-parser as middle-ware.
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Cors for cross origin allowance
const cors = require('cors');
app.use(cors());
// Initialize the main project folder
app.use(express.static('views'));
const port = 8080;
app.use(express.static('dist'));
// Setup Server
const server=app.listen(port, ()=>{console.log(`running on localhost: ${port}`)});
app.get('/all', sendData);
function sendData (request, response) {
response.send(projectData);
};
// TODO-ROUTES!
app.post('/add', Info);
function Info(req, res) {
projectData['date'] = req.body.date;
projectData['temp'] = req.body.temp;
projectData['content'] = req.body.content;
res.send(projectData);
}
module.exports = server;
I made a POST request to /add and it works. Then I call GET /all and also work. The error cannot GET / is because you are requesting an endpoint that not exists.
Just add a dummy data to your Fn() if you want to see some output on GET request wihtout making any post request
Here is my solution
app.get('/', (req, res) => {
res.redirect('/all');
})
we need this because you do not have any root URL set, so we set the root URL to redirect to /all
2nd step then we add a dummy data in the projectData
var projectData = {
date = new Date().getDate();,
temp = 10,
content="This is Test Content"
}
This way when you call 'http://localhost:8080/' you shall get redirected to http://localhost:8080/all and instead of empty {} you shall see the dummy data.

Not getting expected results from included files in Nodejs

I'm not getting expected results by including files in Nodejs. Here is my code:
Service Route File
const express = require('express');
const router = express.Router();
const path = require('path');
const config = require('../config');
const serviceAdapter = require('./serviceAdapter');
module.exports = (preRequestPath, serviceBaseUrl) => {
console.log("On server start", preRequestPath)
router.post('/*', (req, res) => {
console.log("On request", preRequestPath)
const axiosHttp = serviceAdapter(serviceBaseUrl);
axiosHttp.post(preRequestPath+req.path, req.body).then(resp => {
res.send(resp.data)
}).catch(err => {
res.status(404).sendFile(path.join(__dirname + '/../404.html'));
});
});
return router;
}
Main Server File
const express = require('express');
const userApiService = require('./routes/userService');
const userAdminService = require('./routes/userService');
app.use('/api/user_service/', userApiService("/api", config.userServiceUrl) );
app.use('/admin/user_service/', userAdminService("/admin", config.userServiceUrl) );
var server = app.listen(3000, function(){
console.log('Server listening on port 3000');
});
module.exports = server;
Expecting Console Result:
On server start /api
On server start /admin
On request /api (when hitting http://baseurl.com/api/<anything>)
On request /admin (when hitting http://baseurl.com/admin/<anything>)
But Getting Console Output as:
On server start /api
On server start /admin
On request /api (when hitting http://baseurl.com/api/<anything>)
On request /api (when hitting http://baseurl.com/admin/<anything>)
Both the time, returning /api path.
Can anyone tell me why is it happening and what's the solution?
You're creating only one router in userService.js (the first file). It's created once before the function so you really only end up with one router. The first time you require it the router gets created, but the second time you require it Node knows it was already loaded and it's not re-initialized. You should be creating a different router for each case like this:
const express = require('express');
// const router = express.Router(); <-- don't do it here
const path = require('path');
const config = require('../config');
const serviceAdapter = require('./serviceAdapter');
module.exports = (preRequestPath, serviceBaseUrl) => {
const router = express.Router(); // <--- create a new router for each case
console.log("On server start", preRequestPath)
router.post('/*', (req, res) => {
console.log("On request", preRequestPath)
const axiosHttp = serviceAdapter(serviceBaseUrl);
axiosHttp.post(preRequestPath+req.path, req.body).then(resp => {
res.send(resp.data)
}).catch(err => {
res.status(404).sendFile(path.join(__dirname + '/../404.html'));
});
});
return router;
}
Also in your main server file you only need to require it once. It's just a function to create the service so you don't need 2 different variables holding that function. So you can initialize both using the one function like this:
// const userApiService = require('./routes/userService');
// const userAdminService = require('./routes/userService');
const service = require('./routes/userService');
app.use('/api/user_service/', service("/api", config.userServiceUrl) );
app.use('/admin/user_service/', service("/admin", config.userServiceUrl) );

Node Express Cors issue

I cant figure why the cors express middleware wont work. cors, express, and ejs are all saved in package.json. The app works fine if I add corsanywhere proxy on the front end but id like to work around this on the server side. any help much appreciated I've been stuck on this.
the api is in the get View/index path
the error is:
Access to fetch at 'https://api.darksky.net/forecast/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
const express = require('express');
const app = express();
const ejs = require('ejs');
const cors = require('cors');
const PORT = process.env.PORT || 3000;
// app.use((req, res, next) => {
// res.header('Access-Control-Allow-Origin', '*')
// res.header('Access-Control-Allow-Headers', 'Origin', 'X-Requested-With')
// next();
// });
app.use(cors());
app.use(express.static(__dirname + '/Public'));
app.set('view engine', 'ejs');
app.get('/', cors(), (req, res) => {
res.render(__dirname + '/Views/index')
});
app.listen(PORT, () => {
console.log(`server is listening on ${PORT}`)
});
client side:
it works with the ${proxy} in there but id like to get rid of that
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(position => {
long = position.coords.longitude;
lat = position.coords.latitude;
var proxy = 'https://cors-anywhere.herokuapp.com/'
var api = `${proxy}https://api.darksky.net/forecast/042750f3abefefdfe2c9d43cf33ce576/${lat},${long}`;
fetch(api)
.then(response => {
return response.json();
})
.then(data => {
let {temperature, summary, icon,} = data.currently;
temperatureDegree.textContent = Math.floor(temperature);
temperatureDescription.textContent = summary;
locationTimezone.textContent = data.timezone;
setIcons(icon, document.querySelector('.icon'
w
``````
So, if you're trying to access some other service https://api.darksky.net/forecast/ (that you don't control) from your web page, then there is nothing you can do to make CORs work for that. It's up to the api.darksky.net server to decide if CORs is allowed or not. You can't change that.
You could make a request from your web page to your server to ask it to get some data from api.darksky.net for you and then return it back to your webpage (working as a simple proxy). Your server is not subject to any CORs limitations when accessing api.darksky.net. Only browsers are limited by CORs.
And, as you've found, you can also use a proxy service that enables CORs and fetches data for you.
Let's suppose you want to proxy the parts of the darksky API, you could do something simple like this:
const express = require('express');
const app = express();
const request = require('request');
const apiRouter = express.Router();
// maps /api/forecast/whatever to http://api.darksky.net/forecast/developerKey/whatever
// and pipes the response back
const apiKey = "yourAPIKeyHere";
apiRouter.get("/*", (req, res, next) => {
// parse out action and params
// from an incoming URL of /api/forecast/42.3601,-71.0589
// the /api will be the root of the router (so not in the URL here)
// "forecast" will be the action
// "42.3601,-71.0589" will be the params
let parts = req.path.slice(1).split("/"); // split into path segments, skipping leading /
let action = parts[0]; // take first path segment as the action
let params = parts.slice(1).join("/"); // take everything else for params
request({
uri: `https://api.darksky.net/${action}/${apiKey}/${params}`,
method: "get"
}).pipe(res);
});
app.use("/api", apiRouter);
app.listen(80);
Now, when you send this server, this request:
/api/forecast/42.3601,-71.0589
it will request:
https://api.darksky.net/forecast/yourAPIKeyHere/42.3601,-71.0589
and pipe the result back to the caller. I ran this test app and it worked for me. While I didn't see anything other than forecast URLs in the darksky.net API, it would work for anything of the format /api/someAction/someParams.
Note, you probably do NOT want to enable CORS on your server because you don't want other people's web pages to be able to use your proxy. And, since you're just sending requests to your own server now, you don't need CORS to be able to do that.

Express - get value of path matching string

Question
I've got several nested routers, and would like to get access to the whole string that the request's path matched. It's a little hard to say with english, so take a look at this code:
const express = require('express')
const app = express()
const router1 = express.Router()
const router2 = express.Router()
// set up router 2 paths
router2.get('/path2/:param2', (req, res, next) => {
const someVar = req.something // the value I'll talk about in a second
return res.status(200).send({ someVar })
})
// set up router1 paths
router1.use('/path1/:param1', router2)
// connect the routers behind a base url
app.use('/api/v1', router1)
app.listen(3000, function () {
console.log('Example app listening on port 3000!')
})
If I were to make a GET request with:
curl -X GET http://localhost:3000/api/v1/path1/myparam1/path2/myparam2
I want that to return an object like this:
{
"someVar": "/api/v1/path1/:param1/path2/:param2"
}
Context
I have middleware in my app which logs the path to an elasticsearch cluster, and I'd like the cluster to group paths by the string they used to match the request, rather than the request itself. That way I can get a visual of which request endpoints are being hit the most.
// you need to set mergeParams: true on the router,
// if you want to access params from the parent router
var router1 = express.Router({mergeParams: true});

swagger-ui-express Multiple Routes for Different API Documentation

I have 2 separate swagger API documentations which I want to run via swagger-ui-express NPM package, and my express server is starting fine on port 5000, but when I am trying to access any of the URL always getting the 404 error, Here is my app.js file and URL's for your reference:
Route 1: http://localhost:5000/edi
Route 2: http://localhost:5000/ecom
const express = require('express');
const router = require('express').Router();
const swaggerUi = require('swagger-ui-express');
const ediSwaggerDocument = require('./edi-openapi.json');
const ecomSwaggerDocument = require('./ecom-openapi.json');
const SWAGGER_APP_PORT = process.env.SWAGGER_APP_PORT || 5000;
const app = express();
// Route Middleware to be called before serving Any Route
router.use('/', swaggerUi.serve);
// Route - EDI RESTful API Documentaion
router.get('/edi', swaggerUi.setup(ediSwaggerDocument));
// Route - eCommerce RESTful API Documentaion
router.get('/ecom', swaggerUi.setup(ecomSwaggerDocument));
app.listen(SWAGGER_APP_PORT, () => console.log(`RESTful API Up and Running on Port ${SWAGGER_APP_PORT}`));
Try the following configurations to hook swaggerUi with express-app
app.use("/edi", swaggerUi.serve, (...args) => swaggerUi.setup(ediSwaggerDocument)(...args));
app.use("/ecom", swaggerUi.serve, (...args) => swaggerUi.setup(ecomSwaggerDocument)(...args));
I haven't dig enough into the swagger-ui-express but i think the problem comes from the function generateHTML (called in swaggerUi.setup). A global module variable (swaggerInit) is updated when called.
So the last call to generateHTML has side effects on every routes that use swaggerUi.setup middleware.
A quick fix is to generate HTML each time the route is called. According to the code snippet you provide, it should looks like :
let swaggerDocEdi = require('./edi-openapi.json');
let swaggerDocEcom= require('./ecom-openapi.json');
let router = express.Router();
router.use('/api/edi', swagger.serve, (req, res) => {
let html = swagger.generateHTML(swaggerDocEdi);
res.send(html);
});
router.use('/api/ecom', swagger.serve, (req, res) => {
let html = swagger.generateHTML(swaggerDocEcom);
res.send(html);
});
Note that the global variable is still updated.
router.use('/jobs/api/:id',swaggerUi.serve,(req,res)=>{
console.log("here")
let a = req.params.id
if(a==='all'){ res.status(200).send(swaggerUi.generateHTML(swaggerDocument))}
if(a==='google'){res.status(200).send(swaggerUi.generateHTML(GoogleAds)) }
});
I was also looking for a solution for this issue, and found a better solution recommended by Swagger UI Express. check the link below
https://github.com/scottie1984/swagger-ui-express#two-swagger-documents
const express = require('express');
const app = express();
const swaggerUi = require('swagger-ui-express');
const swaggerDocumentOne = require('./swagger-one.json');
const swaggerDocumentTwo = require('./swagger-two.json');
var options = {}
app.use('/api-docs-one', swaggerUi.serveFiles(swaggerDocumentOne, options), swaggerUi.setup(swaggerDocumentOne));
app.use('/api-docs-two', swaggerUi.serveFiles(swaggerDocumentTwo, options), swaggerUi.setup(swaggerDocumentTwo));
Hope this will help others as well.
It looks like the Router is being used incorrectly. For this simple use case I would recommend adding your routes directly to the app instance. See:
const express = require('express');
// xxxx const router = require('express').Router();
const swaggerUi = require('swagger-ui-express');
const ediSwaggerDocument = require('./edi-openapi.json');
const ecomSwaggerDocument = require('./ecom-openapi.json');
const SWAGGER_APP_PORT = process.env.SWAGGER_APP_PORT || 5000;
const app = express();
// Route Middleware to be called before serving Any Route
app.use('/', swaggerUi.serve); // replaced router with app
// Route - EDI RESTful API Documentaion
// REPLACED "router" with "app"
app.get('/edi', swaggerUi.setup(ediSwaggerDocument));
// Route - eCommerce RESTful API Documentaion
// REPLACED "router" with "app"
app.get('/ecom', swaggerUi.setup(ecomSwaggerDocument));
app.listen(SWAGGER_APP_PORT, () => console.log(`RESTful API Up and Running on Port ${SWAGGER_APP_PORT}`));
Now, you could use the Router by adding it to the app instance with app.use(). See:
// Route - EDI
router.get('/edi', swaggerUi.setup(ediSwaggerDocument));
// Route - eCommerce
router.get('/ecom', swaggerUi.setup(ecomSwaggerDocument));
// Adding it to App instance
app.use('/swagger', router)
// End Point => localhost:5000/swagger/edi
Hope this helps!!

Resources