How to serve angular nx app with internal routing from express? - node.js

I have Node.js + Express backend app with some static routes, some internal (api) routes and Angular 11 app on frontend.
Now I'm trying to rebuild frontend using nrwl nx into three independent apps with shared libs but stuck with express routes.
Current version use one static route to serve whole angular app:
app.use("/", express.static(path.join(__dirname, "frontend")));
It works well, but when I'm trying to split routes like
app.use("/crm/admin/", express.static(path.join(__dirname, "crm-admin")));
app.use("/crm/moderator/", express.static(path.join(__dirname, "crm-moderator")));
app.use("/", express.static(path.join(__dirname, "client")));
I've got issues with angular routing inside admin or moderator apps - production build static app won't load anything
If I edit index base href to point to /crm/admin/ it still won't load page for same reason
Only if I remove all hash from filenames app loading but still have ussues with internal routing: It gives ability to log in and then redirect as it assumed in auth effect. but any other routes are not working as they do in serving app and written in root routes:
path: '',
component: AppComponent,
children: [
{
path: 'login',
component: AuthComponent,
},
{
path: 'logout',
component: AuthLogoutComponent,
},
{
path: 'personal',
loadChildren: () =>
import('./components/personal/personal.module').then(
(m) => m.PersonalModule
),
canActivate: [AuthGuardService],
},
{
path: 'orders',
loadChildren: () =>
import('./components/orders/orders.module').then(
(m) => m.OrdersModule
),
canActivate: [AuthGuardService],
},
{
path: '**',
redirectTo: 'personal',
pathMatch: 'full',
},
],
For testing purposes I've already tried to point crm-admin folder to "/" route. It works same as if I reproduce steps written above with broken routing.
Please, point me what I'm missing during building nx angular app with internal routing and trying to serve it from static express route.
Highly unlike to rebuild existing express app into nx controlled express app, if it possible. It works as intended and no needed to any changes.
Thanks in advance.
upd:
tried to add this
app.use("/crm/admin/", express.static(path.join(__dirname, "crm-admin")));
app.get("/crm/admin/", function (req, res) {
res.sendFile(path.join(__dirname, "./crm-admin/index.html"));
});
and rebuild not using --configuration=production. After fixing base href token in index file it works, but still have issues with routing If I try to change path in the browser address bar.
upd2:
If I add
"baseHref": "/crm/admin/",
"deployUrl": "/crm/admin/",
into angular.json into build section, builded app already have base href token pointed to express route, I've want it be served from, as well as it serving locally from localhost:4201/crm/admin but routing errors still present on builded app.

Well. after googling I've found an answer here, on stackoverflow.
Maybe it can be useful for someone else.

Related

File serve in shopify app dev not working

I've created shopify app with node.js app template
npm init #shopify/app#latest
Folder structure is at the bottom
And run npm run dev
It's ok for api endpoints.
What I wanna do is to serve static files. In fact, this is an express.js server and I created a static folder in web folder
app.use(serveStatic('static'));
But I can't access static files. I tried app.use(serveStatic("${process.cwd()}/static")). The above stuff is working on a normal express.js project. But it does not work with shopify cli and vite config.
Vite config is
const config = {
test: {
globals: true,
exclude: ["./frontend/**", "./node_modules/**"],
},
};
export default config;
I finally got it.
I noticed that:
It works if you were to load using localhost:PORT/path/to/static.file. You can print out PORT in your web/index.js.
This simple middleware doesn't get triggered when requesting your static file through ngrok but it does get triggered by number 1 above.
app.use((req, res, next) => {
console.log("Backend hit!");
next();
});
That means the backend never got the static file request. My understanding is vite receives all the requests then proxies some of them to the backend using a config.
The config Shopify gave is not proxying the static file request so you'll need to modify the proxy config.
vite.config
...
export default defineConfig({
...
server: {
host: "localhost",
port: process.env.FRONTEND_PORT,
hmr: hmrConfig,
proxy: {
"^/(\\?.*)?$": proxyOptions,
"^/api(/|(\\?.*)?$)": proxyOptions,
// The next line is what I added
"^/static/.*$": proxyOptions,
},
},
});
web/index.js
app.use("/static", express.static(`${process.cwd()}/public`));
I'm mounting my static files on "/static" but feel free to modify the proxy line to suit your needs.

My CRUD app works locally but not on Heroku

I've created a CRUD app and it works locally, but I can't get it to work fine on heroku. It deploys correctly, the website seems to work, but then I can't get the items I need from the database, as it keeps saying connection refused.
I added the .env variables to Heroku, as well as setting the port to process.env.PORT || 5000 and app.listen(port), I'm not sure what's causing the error. I also have a Procfile with web: node server.js, and a "start" script in package.json that points to server.js. It seems that the server doesn't start at all.
Here the repo in case you want to have a look https://github.com/ThomYorke7/inventory, here the app on heroku https://boardgamenerd.herokuapp.com/
The problem lies in the fact that your application has a backend (server) and a frontend (client) which are served differently locally than on Heroku.
I suppose locally your client is running on localhost:3000 (as it is the default with create-react-app you bootstrapped).
While your backend is running on localhost:5000, your client's package.json contains this line to make it work locally:
"proxy": "http://localhost:5000",
If I visit this page of your app: https://boardgamenerd.herokuapp.com/ > boardgames,
then I face these errors on the browser console:
boardgames-list.jsx:18
Error: Network Error
at e.exports (createError.js:16)
at XMLHttpRequest.p.onerror (xhr.js:83)
xhr.js:178
GET http://localhost:5000/boardgames/ net::ERR_CONNECTION_REFUSED
It tells you that your production version still calls backend on localhost:5000.
I.) First I'd try to fix these fetches by changing to relative URLs.
E.g. the above example (boardgames-list.jsx:18)
❌ your current script has hardcoded localhost fetch at the moment:
useEffect(() => {
axios
.get('http://localhost:5000/boardgames/')
.then((response) => {
setBoardgames(response.data);
setLoading(false);
})
.catch((err) => console.log(err));
}, []);
✔️ make it relative to root by removing "http://localhost:5000":
useEffect(() => {
axios
.get('/boardgames/')
.then((response) => {
setBoardgames(response.data);
setLoading(false);
})
.catch((err) => console.log(err));
}, []);
And it will work on Heroku. In case it wouldn't: see my suggestion below.
II.) Second, a suggestion:
Now your https://boardgamenerd.herokuapp.com/boardgames route uses the following backend endpoint to fetch data: https://boardgamenerd.herokuapp.com/boardgames/
The difference is only the last slash ("/") character which can be confusing and cause more issues later!
It is a best practice to add a differentiator path element to your backend endpoints, like /api/. For example: https://boardgamenerd.herokuapp.com/api/boardgames So you can be sure by first sight which GET request related to the backend and which one to the client.
If you'd go with this solution, you will need to add the following to your server.js:
app.use(express.static(path.join(__dirname, 'client', 'build')))
// required to serve SPA on heroku production without routing problems; it will skip only 'api' calls
if (process.env.NODE_ENV === 'production') {
app.get(/^((?!(api)).)*$/, (req, res) => {
res.sendFile(path.join(__dirname, 'client/build', 'index.html'))
})
}
/^((?!(api)).)*$/ regex skips URLs containing "api" in their path, so they won't be served static as the client/build folder's content - api calls won't be served from static and will work fine.

Problems running an Angular 9 app in NodeJS

I have a working NodeJS server and an Angular 9 app. I'm able to test the app in development mode, it works perfectly.
But, when I build the app with ng build --prod and try to access it with NodeJS server I get several errors about file load:
Refused to apply style from
'http://localhost:8080/styles.09cf2cc3740ba29d305c.css' because its
MIME type ('text/html') is not a supported stylesheet MIME type, and
strict MIME checking is enabled.
GET http://localhost:8080/runtime.689ba4fd6cadb82c1ac2.js
net::ERR_ABORTED 404 (Not Found)
I have a proxy file in the app to redirect all its petitions to NodeJS:
proxy.conf.json
{
"/api/*": {
"target": "http://localhost:8080",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
Am I missing something?
proxy.conf.json aims to provide you an easy way to access to backend by rewriting url in development environment, by using ng serve.
For example, by accessing http://localhost:4200/api, in fact, you access http://localhost:3000/api. (so your backend).
But here, you're issue is how to serve Angular files with NodeJS.
Here is a minimal express code which serves an API endpoint /api, and also static files inside public sub-directory.
const express = require('express')
const app = express()
app.use(express.static('public'));
app.get('/api', function (req, res) {
res.send({ message: 'api ok' })
})
app.listen(3000, function () {
console.log('Example app listening on port 3000!')
})
Copy all files generated by ng build --prod, (see inside dist folder, and your-app-name subfolder) to public folder.
node app.js
You will be able to access your Angular app at http://localhost:3000, and your Angular app will be able to access your API at http://localhost:3000/api.

Nodejs application integrate with Angular application

Nodejs application created using Express (express genrator) and used handlebars as view engine. Created couple of routes and works fine. Application running on port 3000.
Express routes:
...
app.use('/', index);
app.use('/landing', landing);
app.use('/home', home);
app.use('/api', api);
...
There is an admin panel separate application built on Angular
Currently Angular application running on port 4200 and uses APIs from NodeJs application which running on port 3000.
Angular application routes
const routes : Routes = [
{ path: '', redirectTo: '/user', pathMatch: 'full' },
{
path: 'user',
component : UserComponent,
children : [
{ path:'', redirectTo: '/account', pathMatch: 'full' },
{ path: 'account', component: AccountComponent },
]
},
]
NodeJs application folder structure
api/
api.js
bin/
www
modules/
mongoose.js
node_modules/
public/
css/
fonts/
img/
js/
ngapp/ => Angular resources created with ng build
inline.bundle.js
main.bundle.js
polyfills.bundle.js
styles.bundle.js
vendor.bundle.js
routes/
home.js
index.js
landing.js
views/
common/
header.hbs
footer.hbs
layouts/
master.hbs
ngapp/
index.html => Angular index.html file
index.hbs
landing.hbs
home.hbs
app.js
package.json
What I'm trying:
Want to run both NodeJs and Angular application on same port i.e. port 3000.
What I have done:
Ran ng build and placed index.html file inside /views/ngapp/ of nodejs folder structure.
Created one 'user' route in nodejs and serving that index.html file of angular application. (May be this is not a good way)
app.get('/user', function (req, res, next) {
res.sendFile(path.join(__dirname + '/views/ngapp/index.html'));
});
Somehow its loaded but encountered an error:
My question is about how we can integrate Angular Application (may be running on separate route but on the same port) with NodeJs application which already have some routes defined and used view engine to render pages.
There are two possible solutions.
Use your node application to serve the static frontend files. Then you can't really use ng serve (this is probably what you'd do when running live).
You should be able to tell Express to serve static content from an Angular build directory like this:
app.use(express.static('../angular/dist'));
Which would work if you had a file structure like so and were running serve.js with Node:
-node server
-serve.js
-angular
-dist/*
You can customize as needed by configuring the Angular build folder to be wherever you need it, or use Grunt/Gulp to move files around to the folders you prefer with a build task.
Use nodejs with a different port, and use Angular's proxy config, to have Angular think the api port is actually 4200 (this is probably best during development).
This is primarily a concern during development I reckon, since you most likely wont (and shouldn't) be using ng serve live, so option 2 would be my best recommendation.
To configure a proxy, you create a file in your angular application root directory called proxy.config.json with the following content:
{
"/api/*": {
"target": "http://localhost:3000",
"secure": false,
"changeOrigin": true
}
}
Then when you run ng serve, you run it with ng serve --proxy-config proxy.config.json instead.

Angular CLI routing node_modules

I'm currently getting my feet wet in angular4 with the angular cli environment, and I'm trying to import the bootstrap.css from my modules in a way that feels right.
I'm currently importing it from my app.component.css like so:
#import "../../node_modules/bootstrap/dist/css/bootstrap.min.css";
This works, but having a relative path to a folder that should really be hidden doesn't sit well with me.
In the past I've used node to get around this by using an express static route:
var express = require('express');
var app = express();
app.use('/scripts', express.static('node_modules'));
But since angular CLI seems to cut node out of the development process, I'm looking for an alternative way to do that.
I tried adding to my module.ts:
import { RouterModule } from '#angular/router';
imports: [
RouterModule.forRoot([
{ path: 'scripts', redirectTo: './node_modules', pathMatch: 'full' }
])
]
But this dosn't seem to be doing anything. I'm not sure if I'm referencing my paths incorrectly, or I'm misusing the router module all together.
Is there a better way to bring in 3rd part assets?
Angular CLI will compress all CSS code into a couple of files after the build. You don't have to expose bootstrap.min.css though angular routes for it to work.
Rather, you can include it in configuration file.

Resources