Prevent Angular 6 router from overriding routes defined in Express Server - node.js

How do I prevent Angular routing from interferring with routes from an Express Node Server?
I'm setting up some routes ( to node-RED middleware) in my Express server:
server.js
// Serve the editor UI from /red
app.use(settings.httpAdminRoot,RED.httpAdmin);
// Serve the http nodes UI from /api
app.use(settings.httpNodeRoot,RED.httpNode);
// Angular DIST output folder
app.use(express.static(path.join(__dirname, 'dist')));
// Send all other requests to the Angular app
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.js'));
});
but in my router module they get always overridden ( with our without the default path redirection)
routing.module.ts
const appRoutes: Routes = [
{
path: "example-page",
loadChildren: "../modules/example/example.module#ExampleModule?sync=true"
},
// Last routing path specifies default path for application entry
/* {
path: "**",
redirectTo: "/example-page",
pathMatch: "full"
},*/
];
I'm only providing this little code because I want to ask in general how do I prevent Angular from interferring with routes defined in an Express server and what is the best practice for routing in an Express/ Node.js + Angular + AspNetCore + Webpack app.

If you're using Angular, then let Angular handle all pages. This code takes care of that and hence angular is handling all routes.
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.js'));
});
If you want express to handle some routes instead of angular, handle that route before handling angular route as follows:
app.get('/some-non-angular-path', (req, res) => {
//code to handle this path. once it is handled here, angular won't be involved
//non-angular-paths should come first in the order
});
app.get((req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.js'));
});

Related

express route and react route have conflict - it is returning api result on heroku

first of all it's working proper in my local but it works the way i don't want on heroku.
I working on a MERN stack app and I have express path same time react router path.
https://test.herokuapp.com/project
in my node.js express side
server.js
--------
/* Router */
import routes from "./routes/index.js"
app.use('/', routes)
/* For Deployment */
if(process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, './../client/build')))
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, "../client/build/index.html"));
})
}
routes/index.js (it returns project data from mongodb)
---------------
router.get( '/project', ProjectController.getAll);
at the same time i have a another route in my react side
const routers = (isUserAuth, isAdminAuth) => [
{
path: '/project',
exact: true,
element: <Project />
}
]
http://localhost:3000/project
that route when react router redirect with a NavLink working and if page refresh manually still working in my local. But when it comes to heroku platform it's working redirect with NavLink but it doesn't work when refresh the page. When refresh the page as i said it returns project data from express serve nodejs side.
I think it's about the first code block I put above. thanks..

NodeJS Express Angular protect Frontend by middleware

I have a NodeJS Backend running, which uses the express framework.
In the frontend I use Angular. In the past I build two Docker Containers (one frontend, one backend). Now I want to build just one container, in which NodeJS run as usual. But it should also provide my frontend. It is working, so here is my code:
import express = require("express");
import session = require('express-session');
import dotenv from 'dotenv';
dotenv.config();
const app = express();
const port = process.env.PORT || 80;
...
const isOnline: RequestHandler = (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (!req.session.user) {
return res.redirect('/auth/login?rd=' + req.originalUrl);
}
next();
};
...
app.use('/api', isOnline, apiRoutes);
app.use('/auth', authRoutes);
app.get('*.*', express.static(path.join('./', 'dist'), {maxAge: '1y'}));
app.get('*', isOnline, (req: express.Request, res: express.Response) => {
res.status(200).sendFile(`/`, {root: path.join('./', 'dist')});
});
app.listen(port, () => console.log('API running on port ' + port));
Code explaination
So actually there are three types of routes. The API, the Authorization and the Frontend paths. The API and the Frontend should be only accessible by users which are authorized. The Authorization is available for everyone.
Issue / Question
The issue is, that I provide all files from the dist folder (in which my build angular app is located), which has a dot (so all file names).
And when I open the app (example.com), I will directly redirect to the auth route. This means the middleware is working. But if add the index file to the path (example.com/index.html), I have access to the app directly, because it is provided by the . route.
Solution Ideas
Is there maybe any way to exclude the index.html from the wildcard statement (or do I stop providing angular by this)?
Can I catch the route exmpla.com/index.html and redirect to the base path?
Or is there any other way to protect my whole angular app by my middleware?
you might completely close public (dist) folder:
app.get('*.*', isOnline, express.static(path.join('./', 'dist'), {maxAge: '1y'}));
leaving publicly open only api and login route
and on the login route in authRoutes, render a login page with some minimal inlined css and form or javascript to handle the login:
authRoutes.get('login', /*res.sendFile with styles and login form*/)
authRoutes.post('login', /*handle login*/)
//...

How to resolve 'cannot GET' error on react routes when using ExpressJS to serve react build?

I have the following folder structure for a React client and ExpressJS server setup. I'm trying to serve the react build files from the Express server. I also have defined some route handlers for API from the express server
server /* NodeJS server folder
-node_modules
-app.js
-package.json
client /* React client folder
-build
-node_modules
-src
-components
-App.js
-index.js
-package.json
My express (app.js) has the following code
app.use(express.static(path.join(__dirname, '../client/build')));
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname, '../client/build', 'index.html'));
});
...
...
app.get('/data', (req, res) => {
res.json(data);
});
Here I'm able to see the home page of my React application but all the routes throws Cannot GET /reactroute. I'm also able to access the /data as defined by app.get handler
Referring some other answers I changed my code in the express server as
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, '../client/build', 'index.html'));
});
...
...
app.get('/data', (req, res) => {
res.json(data);
});
At this time I'm able to access the routes from the react but the /data is not giving the JSON (API) instead it is acting like an another route.
What changes need to be done to get both react routes from the build folder and the route handlers defined in the express.
You have to place get /data before get *.
Like this:
app.get('/data', (req, res) => {
res.json(data);
});
...
...
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, '../client/build', 'index.html'));
});
If you place get * first, it also includes /data. Where * considers all routes which are not mentioned before it.

What does a '*' mean as a route in app.get()?

I am deploying a PERN (PostgreSQL, Express, React, Node) on Heroku. I watched a tutorial on YouTube and he has some kind of this code for deploying to production:
if (process.env.NODE_ENV == "production") {
const path = require("path");
app.use(express.static(path.join(__dirname, "./build")));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "build", "index.html"));
});
}
So I want to ask what does the * route mean in app.get(...) does it work same with the path / route or something else?
Here * is a wild card. This means this router will handle not just /, but any request that starts with / - be it /about, /products, or even /product-details. That * (wildcard) stands for "any route path can be here", whatever link you'll access through a GET request, will trigger that middleware

Serving multiple react apps with client-side routing in Express

I have different software products for one single service, which needs to be deployed to a single server. The clients are built with react, with a build setup by create-react-app, while the server runs Node.js and Express.
When I serve a single application from the server it is done the following way:
// App.js
// ...
// Entry point for data routes (API)
app.use('/data', indexRoute);
if(process.env.NODE_ENV !== 'development') {
app.use(express.static(path.join(__dirname, 'build-client')));
app.get('/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
}
I want to be able to serve multiple apps from the server. How should I do that?
What I tried is to wire in different static paths for the assets and separate the clients with different names, although it did not work. Like this:
// App.js
// ...
// Entry point for data routes (API)
app.use('/data', indexRoute);
if(process.env.NODE_ENV !== 'development') {
app.use(express.static(path.join(__dirname, 'build-client')));
app.use(express.static(path.join(__dirname, 'build-admin')));
app.get('/client/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
app.get('/admin/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
}
I have also tried to do it this way, but Express throw Error: No default engine was specified and no extension was provided:
if(process.env.NODE_ENV !== 'development') {
// Admin paths
app.use('/admin', express.static(path.join(__dirname, 'build-admin')));
app.get('/admin/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-admin' , 'index.html'));
});
// Site paths
app.use('/', express.static(path.join(__dirname, 'build-client')));
app.get('/*', function(req, res) {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
}
How could I accomplish this or something similar?
After some tinkering I was able to achieve this without using virtual hosts. I used the first idea you gave in the question, except I left the main app at the root (i.e. /).
// when going to `/app2`, serve the files at app2/build/* as static files
app.use('/app2', express.static(path.join(__dirname, 'app2/build')))
// when going to `/`, serve the files at mainApp/build/* as static files
app.use(express.static(path.join(__dirname, 'mainApp/build')))
// These are necessary for routing within react
app.get('app2/*', (req, res) => {
res.sendFile(path.join(__dirname + '/app2/build/index.html'))
})
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/mainApp/build/index.html'));
});
After this, I went into mainApp/package.json and added
"proxy": "http://localhost:4141"
:4141 is the port that the express server is running on. This line will make calls to fetch('/some/route') go back to the server instead of into your react app itself.
Finally, we go to app2/package.json and add
"proxy": "http://localhost:4141/app2",
"homepage": "/app2"
I believe that the key here is the "homepage" key. The way I understand it, when react starts it searches for some static files at its homepage, and without the "homepage" piece I was only able to get either a blank white screen or the mainApp.
I hope this helps someone out there!
EDIT
I have since changed from serving my create-react-apps through my express server to serving them through netlify. Now I don't need to worry about this express setup, or the homepage key in package.json. The express server lives by itself, and the react apps can still both use the same api, and deployment is much easier. Setup with netlify is trivial.
After struggling for a while with this problem I've found a possible solution without compromising the original setup.
We used Express vhost package to setup handling of requests through virtual domains.
When you create your app instance, you should initialize as many apps with express as you want to expose separately (in our case its three separate apps plus the original app instance)
// Create an express instance
const app = express();
const appAdmin = express();
const appClient = express();
const appVendor = express();
After that you need to install vhost and import it. Then with specifying the static folder for each app you can handle serving the static files separately, while the remaining part deals with handling the request for the given subdomains respectively.
appAdmin.use(express.static(path.join(__dirname, 'build-admin')));
appClient.use(express.static(path.join(__dirname, 'build-client')));
appVendor.use(express.static(path.join(__dirname, 'build-vendor')));
appAdmin.use((req, res, next) => {
return res.sendFile(path.resolve( __dirname, 'build-admin' , 'index.html'));
});
appClient.use((req, res, next) => {
return res.sendFile(path.resolve( __dirname, 'build-client' , 'index.html'));
});
appVendor.use((req, res, next) => {
return res.sendFile(path.resolve( __dirname, 'build-vendor' , 'index.html'));
});
app.use(vhost('domain.com', appClient));
app.use(vhost('www.domain.com', appClient));
app.use(vhost('a.domain.com', appAdmin));
app.use(vhost('b.domain.com', appVendor));
Don't forget to add the desired subdomains in your domain's DNS registry. Example:
...records
CNAME vendor #
CNAME admin #

Resources