Running frontend and backend on the same port - node.js

I am having a problem today that has something to do with routing. I have two main codes: one is the frontend and the other one is the backend.
The frontend is written using Vue.js so it's a SPA. This webapp is kind of complex and involves a lot of routing and backend AJAX API calls.
// All imports
import ...
loadMap(Highcharts);
loadDrilldown(Highcharts);
boost(Highcharts);
Vue.config.productionTip = false
Vue.use(VueCookie);
Vue.use(ElementUI, {locale});
Vue.use(VueRouter);
Vue.use(VueHighcharts, {Highcharts });
Vue.use(HighMaps);
// This is a global component declaration
Vue.component('app-oven', Devices);
Vue.component('app-sidebar', SideBar);
Vue.component('app-header', Header);
Vue.component('app-footer', Footer);
Vue.component('app-query', Query);
Vue.component('app-deviceproperties', DeviceProperties);
Vue.component('app-device', Device)
Vue.component('app-queryselection', QuerySelection)
Vue.component('app-index', Index)
Vue.component('app-index', Error)
Vue.component('app-realtime', RealTime);
Vue.component('app-login', Login)
Vue.component('app-preferences', Preferences)
const routes = [
{ path: '/index', component: Index},
{ path: '/', component: Login},
{ path: '/device/:deviceId', component: Device},
{ path: '/preferences', component: Preferences},
{ path: '*', component: Error}
];
const router = new VueRouter({
routes: routes,
mode: "history" // Gets rid of the # before the path
})
new Vue({
el: '#app',
router: router,
components: { App },
template: '<App/>'
})
The backend is written using Express on Node.js and it answers to specific AJAX calls from the Frontend.
// All imports
import ...
function prepareApp() {
let app = new Express();
app.use(cors({
origin: "*",
allowedHeaders: "Content-type",
methods: "GET,POST,PUT,DELETE,OPTIONS" }));
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.use(helmet());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
// Get all parameters
app.get('/params', params.parameters);
// Get all devices ever seen on the databases
app.get('/devices', params.devices);
app.get('/organizeData', organizer.updateAll);
// WebApp used services to access various things
app.post('/customQuery', stats.query);
app.post('/statistics', stats.statistics)
app.post('/getUserInfo', stats.getUserInfo)
app.post('/setUserInfo', stats.setUserInfo)
app.post('/genericQuery', stats.genericQuery)
app.post('/NOSQLQuery', stats.NOSQLQuery)
// Users check and insertion
app.get('/insertUser', stats.insertUser)
app.post('/verifyUser', stats.verifyUser)
app.get('/', errors.hello); // Returns a normal "hello" page
app.get('*', errors.error404); // Catch 404 and forward to error handler
app.use(errors.error); // Other errors handler
return app;
}
let app = prepareApp();
//App listener on localhost:8080
app.listen(8080, () => {
console.log("App listening on http://localhost:8080");
});
I only used this setup during development so I had both running at the same time on localhost with a different port for both. Now I would like to start the production cycle but I have no idea where to start.
Most importantly I am deploying both applications onto a Virtual Machine that is running on an external server. It already has a DNS association and a static IP address so that is already covered.
The problem arises when I try to run both programs at the same time on this production machine since its open ports are only the port 80 and the port 443. I think this is pretty normal in a production environment but I don't know how to adapt my applications so that they can still talk to each other and retrieve useful information from the Database while still using a single port.
I hope I explained the problem kinda well. Looking forward to a nice (and maybe long) answer.

I'd recommend running the backend on port 3000 internally and have nginx listening on 80 and 443 and proxying urls starting with '/api' to 3000 and deliver the frontend directly since it's just a bunch of static files.
This would be your nginx configuration. Just make sure backend server has some api prefix like '/api'. Build your vuejs app with 'npm run build' and copy the folder to /opt/frontend.
upstream backend {
server 127.0.0.1:3000;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
location /api/ {
proxy_pass http://backend;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location / {
root /opt/frontend/dist;
try_files $uri $uri/ /index.html;
}
}
Alternatively, you could use the backend to host the frontend. However, a webserver like nginx is more efficient at serving static files than your backend api server.

If you don't have a way to open more ports, you can build your frontend into production mode and then take its index.html and dist folder to the same folder where your nodejs app are.
Then you create a express app listening to port 80 and send the HTML file.
var express = require('express');
var app = express();
var path = require('path');
var dir = '//vm//path//here';
app.get('/', function(req, res) {
res.sendFile(path.join(dir + '/index.html'));
});
app.listen(80);

In my case, my backend server doesn't run in cluster mode( e.g. with 3001, 3002... together with 80 port)
My case: rails server running with passenger ( mydomain.com , 80 port )
and I need to run my Vuejs project with the same domain ,the same port.
so the only solution is to run vue in specified URL.
this is my solution:
1.change your nginx config.
http {
# our backend app is passenger( rails server, running on 80 port)
passenger_root /usr/local/rvm/gems/ruby-2.2.10/gems/passenger-6.0.0;
passenger_ruby /usr/local/rvm/gems/ruby-2.2.10/wrappers/ruby;
include mime.types;
default_type application/octet-stream;
server {
listen 80;
passenger_enabled on;
# we are using this folder as the root of our backend app.
root /mnt/web/php/public;
charset utf-8;
location ~ ^/(images|javascripts|stylesheets|upload|assets|video)/ {
root /mnt/www/php/public;
expires 30d;
add_header Cache-Control public;
add_header ETag "";
}
# vuejs related content
location /vue.html {
root /mnt/web/vuejs/h5/dist;
}
location /static {
root /mnt/web/vuejs/h5/dist;
}
}
}
2.in your vue project's dist folder:
$ mv index.html vue.html
3.all the requested url in your vuejs project should be changed according to the nginx config.

Related

react net::ERR_CONNECTION_REFUSED when try to GET data from backend node on localhost

I have a frontend react that uses axios to get data from a separate node server. The frontend uses a .app domain with SSL certificate but the backend http://localhost:3001, vanilla http, IP address, and port.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
...
axios({
method: 'GET',
url: 'http://localhost:3001/',
params: {page: pageNumber, limit: limit},
cancelToken: new axios.CancelToken(c => cancel = c)
}).then( res => {
setSermons( prevSermons => {
return [...new Set([...prevSermons, ...res.data.map( sermon => sermon )])]
})
setHasMore(res.data.length > 0)
setLoading(false)
}).catch( e => {
if (axios.isCancel(e)) return
setError(true)
})
return () => cancel()
}, [query, pageNumber, limit] )
... and here is my backend node/express server:
const express = require('express')
const cors = require('cors')
const knex = require('knex')
require('dotenv').config()
const db = knex({client: 'pg', connection: <...connection stuff...>})
const app = express()
app.use(express.urlencoded({ extended: false }))
app.use(express.json())
app.use(cors())
app.get('/', (req, res) => {
let page = req.query.page || 0
let limit = req.query.limit || 50
db.select('*')
.from('sermons')
.limit(limit, {skipBinding: true})
.offset(limit*page)
.then( (data) => {
res.json(data)
})
.catch( (err) => {
console.log(err)
})
})
const port = process.env.APP_PORT
app.listen(port, '0.0.0.0', () => console.log(`Server running on port ${port}, http://localhost:${port}`));
I can open both the frontend and backend parts of the site on my browser. The backend is accessible via http://157.xxx.xxx.xxx:3001 IP and the frontend maps to my domain. But the frontend can't retrieve data from the backend.
All of this is running behind an nginx reverse proxy. I did not find any firewalls installed on the server. Yesterday it was working but overnight the connection refused error started. I know that previously, I left the localhost out of the nginx setup entirely.
It seems like CORS is not working, even though the node server is importing/using it. What more can I look at to debug this?
try adding "proxy": "http://localhost:3001" to your package.json
it will proxy your backend and resolve CORS issues. You can read more about it in this blog post:
https://medium.com/bb-tutorials-and-thoughts/react-how-to-proxy-to-backend-server-5588a9e0347
it does sound like a Cors problem
here is a quick video explaining on how cors works
https://www.youtube.com/watch?v=4KHiSt0oLJ0&ab_channel=Fireship
try adding a proxy in the package.json or set a header like this
res.setHeader('Access-Control-Allow-Origin', '*');
Turns out I couldn't get the backend server to work with http://localhost:3000 but it did work if I mapped it to a true external API with https. This is the final configuration that worked:
nginx.conf file for JUST the backend server. Previously it was absent from nginx.conf because I was trying to make it a localhost only endpoint.
server {
listen 443 ssl; # using port 3000 wont work on some wifi, so incoming is 443 (ssl)
# server_name 157.xxx.xxx.xxx; # gets blocked because mixed content http / https
server_name <app.mydomain.com>;
ssl_certificate /etc/letsencrypt/live/<app.mydomain.com>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<app.mydomain.com>/privkey.pem;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:3001; #<--- react-backend server listens on this port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
How the frontend react app uses axios to talk to backend. The backend endpoint is now HTTPS, with a separate, valid SSL certificate for that the <api.mydomain.com> subdomain:
axios({
method: 'GET',
mode: 'cors',
url: 'https://api.<mydomain.com>',
params: {page: pageNumber, limit: limit}, // { q: query, page: pageNumber },
changes to my node/express backend server endpoint
const express = require('express')
const knex = require('knex')
require('dotenv').config()
const cors = require('cors')
var corsOptions = {
origin: '*',
optionsSuccessStatus: 200
}
app.use(cors(corsOptions))
const db = knex({...config stuff})
app.get('/', (req, res) => {
db.select(... --> ...res.data)
res.setHeader('Acess-Control-Allow-Origin', '*');
})
const port = process.env.APP_PORT
app.listen(port, '0.0.0.0', () => console.log(`Server running on port ${port}, http://localhost:${port}`));
I think many of these changes were not needed, but this is a working state, over https.
Next I'll see if I can block access to this backend to everyone except the frontend domain.

Socket.io and nginx CORS on production

I am trying to get an app that uses socket.io v.3.1.1 to work on production.
It works well on development using webpack devServer for the client on 3000 and nodemon for the server on 4000.
But when I put it on the production server the client complains with:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5003/socket.io/?EIO=4&transport=polling&t=NUmy2Us.
Server
import express from 'express'
import { createServer } from 'http'
import { Server } from 'socket.io'
const app = express()
const prod = process.env.NODE_ENV === 'production'
const port = process.env.PORT || prod ? 5003 : 4000
const httpServer = createServer(app)
const io = new Server(httpServer, {
cors: {
origin: '*',
methods: ['GET', 'POST']
}
})
const connections = []
io.on('connection', (socket) => {
connections.push(socket)
console.log(`Socket id ${socket.id} connected`)
socket.on('disconnect', () => {
connections.splice(connections.indexOf(socket), 1)
})
})
httpServer.listen(port, () => console.log(`App listening on port ${port}.`))
....
Client
...
import { io } from 'socket.io-client'
const port = process.env.NODE_ENV === 'development' ? '4000' : '5003'
const socket = io(`http://localhost:${port}`)
This set up does work on development but when I put it on production on port 5003, it throws the CORS.
On the nginx server blocks I got
location /thisapp/ {
auth_basic $authentication;
auth_basic_user_file /var/www/.htpasswd;
try_files $uri $uri/ =404;
}
# And other proxies for express routing
location /api/process {
proxy_pass http://localhost:5003/api/process;
}
location /api/process/download {
proxy_pass http://localhost:5003/api/process/download;
}
I know the app is listening on 5003 on the server.
Pm2 log App
App listening on port 5003.
When I look at the network on the web sockets tab
On Dev I get this:
And on Production this:
The production server runs on https with let's encrypt but this has never been an issue for other apps I have run, I wonder if socket.io needs me to do something about it thou.
I tried multiple combinations of different approaches but I always get this:
I just ran into this last week - though not with Socket.io - so hopefully I can help.
Before the answer, a link to point you towards some reading on what's going on: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#how_to_allow_cross-origin_access
If your NGINX has access to use more_set_headers then try adding this inside your location block:
more_set_headers 'Access-Control-Allow-Origin: *';
If that works, you can next try paring that back further to:
more_set_headers 'Access-Control-Allow-Origin: http://localhost:5003';
If you don't have access to more_set_headers you can use add_headers instead, but it's confusingly named. It doesn't only add headers; it will also remove any headers applied by blocks further up the hierarchy, like in your server block. more_set_headers will not remove those headers and is truly additive.
The syntax differs a bit. If you're forced to use add_headers try:
add_header Access-Control-Allow-Origin *;
Or more restrictive:
add_header Access-Control-Allow-Origin http://localhost:5003;
Finally, if you need to support multiple origins you can do it like this to have NGINX automatically return a header that is compatible with the origin making the request.
Outside your server block:
map $http_origin $allow_origin {
~^(http://localhost:5003|http://example.com)$ $http_origin;
}
Inside your location block:
add_header Access-Control-Allow-Origin $allow_origin;
I'm not 100% sure about the syntax for using map together with more_set_headers but this should get you 95% of the way there.
In the end after a lot of back and forth it turned out not to have anything to do with the headers.
I think my problem was twofold.
On Dev I am using webpack devServer for the front end on port 3000 and nodemon for the backend on 4000, so I was not using Nginx or
Pm2, and it worked just fine.
Therefore on production I did not have any block for socket.io and Pm2 was running in cluster mode with two instances, the moment I changed it to a single instance on Pm2 and added the Nginx location block for socket.io, it started to work with this:
Server
import express from 'express'
import { createServer } from 'http'
import { Server } from 'socket.io'
const app = express()
const prod = process.env.NODE_ENV === 'production'
const port = process.env.PORT || prod ? 5003 : 4000
const httpServer = createServer(app)
const io = new Server(httpServer)
// Or to make it also work on Dev
const io = new Server(httpSever, { cors: true })
const connections = []
io.on('connection', (socket) => {
connections.push(socket)
console.log(`Socket id ${socket.id} connected`)
socket.on('disconnect', () => {
connections.splice(connections.indexOf(socket), 1)
})
})
httpServer.listen(port, () => console.log(`App listening on port ${port}.`))
Nginx
location /socket.io/ {
proxy_pass http://localhost:5003/socket.io/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
Client
import { io } from 'socket.io-client'
const socket = io()
// Or to make it also work on Dev
const dev = process.env.NODE_ENV === 'development'
const socket = dev ? io('http:localhost:4000') ? io()

Error when refreshing the same page twice

I'm trying to deploy an angular app on a remote server using nodejs and nginx.
I built the application and I serve it with a node app. Nginx acts here as a reversed proxy.
I can acess the website and navigate without an issue. However, when I try to refresh the current page, my browser canno't find it anymore : response get a 404.
But if I enter the domain in the url bar, I can access again to the website.
Does anyone know where I made a mistake ?
Here's the code and the config for the node app and for nginx :
var express = require('express');
var app = express(); // create our app w/ express
var morgan = require('morgan'); // log requests to the console (express4)
var bodyParser = require('body-parser'); // pull information from HTML POST (express4)
var methodOverride = require('method-override'); // simulate DELETE and PUT (express4)
const mongoose = require("mongoose");
const dotenv = require("dotenv");
const mustacheExpress = require("mustache-express");
dotenv.config();
// configuration =================
mongoose.connect(process.env.DB_CONNECT, { useNewUrlParser: true }, () => {
console.log("connected to DB !");
});
app.use(express.static("../front-end/dist/website"));
app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.urlencoded({'extended':'true'})); // parse application/x-www-form-urlencoded
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(methodOverride());
// Middleware
app.use(express.json());
// Templating
app.engine("html", mustacheExpress());
//import routes
const authRoute = require("./routes/auth");
const devisRoute = require("./routes/devis");
const messageRoute = require("./routes/message");
const actuRoute = require("./routes/actu");
//Routes middlewares
app.use("/api/user", authRoute);
app.use("/api/sendDevis", devisRoute);
app.use("/api/message", messageRoute);
app.use("/api/actu", actuRoute);
// listen (start app with node server.js) ======================================
app.listen(3000);
server {
root /var/www/domain.com/html;
index index.html index.htm index.nginx-debian.html;
server_name domain.com www.domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem; # m anaged by Certbot
ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = www.domain.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = domain.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name domain.com www.domain.com;
return 404; # managed by Certbot
}
This is an easy trap to fall into when doing client-side routing with a Single Page App.
The trick is this: when you do you navigating in the browser, the browser is not making requests to your server. When you refresh the page, the browser does send the request to the server. Since you're making an SPA, all of the information about what's on the page is in your Javascript, and that Javascript is loaded in your index.html file. The only file that exists on your server is your root file, and the express.static middleware maps URLs onto your files, so there is no file for it to find (hence the 404).
What you need server-side is to always serve up your index.html file no matter what html file is requested.
The easiest solution is add a fallback handler last in your routing using Express's build-in .sendFile() function. The simple, nuance-free way would be something like this:
// ... ^^ all your other routes
router.get("*",
// The '*' means match *everything*
(req,res)=>{
res.sendFile('index.html'); // Probaby need to set some options here to start in the right directory...
}
);
You may want to use different middleware or add your own bells and whistles for including compression, etc, but the idea would be the same.

How nginx proxy to express(node.js)?Why res.data is index.html?

I wrote a Node.js server project by Express framework.It's all right at localhost.But it's have some problem when I build at remote server.(centos 6.5; ngnix 1.11.6; express 4.14.1; node 6.9.5)
It's nginx.conf.
listen 80;
server_name www.cheeseyu.cn cheeseyu.cn;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://127.0.0.1:3009;
proxy_redirect off;
}
location ~ .*.(gif|jpg|jpeg|png|bmp|swf|js|css|woff|ttf|TTF|svg)$ {
root /home/www/blog;
if (-f $request_filename) {
expires 100d;
break;
}
}
error_page 405 =200 #405;
location #405 {
proxy_method GET;
proxy_pass http://static_resource;
}
#error_page 404 /404.html;
There are info of xhr.
enter image description here
enter image description here
It's node.js.
var express = require('express');
var path = require('path');
var app = express();
var bodyParser = require('body-parser');
var routes = require('./routes');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.all('', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "");
res.header("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
routes(app);
app.set('port', process.env.PORT || 3009);
app.listen(app.get('port'), function() {
console.log('Express server listening on port ' + app.get('port'));
});
So my question are :
a. Why status is 200,even I stoped the back-server?
b. why response data is html(content is index.html)?
c. why status still is 405,when I use the post?I have tried any
methods that I finded.
d. Why status is 200,but after request don't use '.then',but use
'.catch'?
e. How nginx proxy to express(node.js)?(I think above all problem is
nginx didn't proxy request to node server.)
If you want to know details about response,you can visit cheeseyu.cn
Thank you help :)
I can show you what i use in nginx conf
location / {
proxy_pass http://your-domain.com:3009;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
this and a process manager is what i needed to get my first node app running on a server.
As process manager i used stableloop.
Important: U also have to check on witch port your node process is running and fit ur ports to that.
Hope that helps you bit.
(and you have to $~ service nginx reload after all changes)
This setting don't have any problem.Just because I reopen nginx,but it no use.You should stop nginx and open nginx

Nginx proxy pass subdomain, node vhost

I have some trouble with nginx proxy_pass redirection on localhost subdomain. I have a domain "domain.com", i want to redirect all request on *.domain.com on *.localhost:9000. Then node handle all request on *.localhost:9000 to the good express app.
On nginx conf when i try the following :
server {
server_name extranet.domain.com;
listen 80;
location / {
proxy_pass http://extranet.localhost:9000;
}
}
Request on extranet.domain.com are well redirected to the good express webapp.
With this :
server {
server_name ~^(.*?)\.domain\.com$;
listen 80;
location / {
proxy_pass http://localhost:9000/$1;
}
}
Express app running on localhost:9000 handle request /mysubdomainname, which implie that regex is good.
But when i try :
server {
server_name ~^(.*?)\.domain\.com$;
listen 80;
location / {
proxy_pass http://$1.localhost:9000;
}
}
All request on *.domain.com return http code 502.
Why http://localhost:9000/$1; works and not http://$1.localhost:9000; ?
(all subdomain are set in /etc/hosts).
Thanks in advance. I'm totally lost !
When a host name isn't known at run-time, nginx has to use its own resolver. Unlike the resolver provided by OS, it doesn't use your /etc/hosts file.
Maybe this will give you a hint, I wanted to pass the subdomain from Nginx to an Express app. Here is my code:
nginx.conf
http {
upstream express {
server localhost:3000;
}
domain.com inside nginx/sites-available
server {
listen 80;
server_name ~^(?<subdomain>.+)\.domain\.com$;
location / {
proxy_set_header Subdomain $subdomain;
proxy_set_header Host $host;
proxy_pass http://express;
}
}
Express app index.js
var express = require('express');
var app = express();
app.get('/', function (req, res) {
const subdomain = req.headers.subdomain;
});
app.listen(3000, function () {
console.log('Example app listening on port 4000!');
});

Resources