Deploy production build of ReactJS with Node express as backend - node.js

Hi even after lot of search i am still confused what is correct way to deploy my react app created using create-react-app with express as backend.
I ran npm run build which created build folder. I copied the build folder to be served as static folder of express and had put
app.use(express.static('build'));.
It is working fine for homepage, that is homepage opens when i run my express node server but when i go to anyother link outside homepage it gives 404.
Everything is working fine in developer mode, which i run by npm start command. I just want to know what i am doing wrond here. Let me know anymore info required to understand the problem. Thankyou.

It sounds like you don't have the backend server running. You need to npm start your server, and then npm start your front end if that make sense. They are 2 separate things.

Are you using client-side routing? A popular implementation of that is react-router.
Let say you are trying to access /page1, what client-side routing does is use the JS to toggle between different components to "fake" the routing, instead of rending a new HTML.
Yet, by default when you change routes, the browser does the usual stuff and send a GET request to the server asking for the corresponding HTML file. But since you only have index.html served, that's why you received 404.
You need to add the following at the end of your app.js, right before you call app.listen of your express server to tell the server to always return index.html no matter what route does it received.
/* client-side routing.
* For GET requests from any routes (other than those which is specified above),
* send the file "index.html" to the client-side from the folder "build"
*/
app.get("*", (_, res) => res.sendFile("index.html", { root: "build" }));
// your usual app.listen
app.listen(port, () => console.log("Listening"));

Related

How can I connect my NodeJS/Express backend to my VueJS frontend using only one port on my local machine?

My Vue app is set up using Vue CLI (Webpack) and it's working as it should. My NodeJS/Express REST API is also working properly. However, to run them simultaneously I now start a local server for each of them -- each with its own port. I would like to have both of them communicate over one port.
Localhost:8080 should point to the home page of my Vue App and the API requests should follow localhost:8080/api/...
In my production environment I use one and the same port/URL by serving the Vue App as a set of static files ('dist' folder). In my development environment I don't know how to set this up, however.
I looked around for answers online, but feel lost among all the different terms I have come across (.env, crossenv, nginx, cors) and that I am running in circles.
What would be a good way of setting this up?
Thank you
Edit:
I ended up creating three modes to run my application:
Development
I use one script in a package.json to start the frontend and backend server on different ports, using pm2 to run the servers in the 'background' rather than blocking further commands in the terminal/cmd. I use configured a proxy inside my vue.config.js to redirect my API calls made in the frontend to the right base URL and used cors as middleware to allow requests to my API from other domains/ports.
Staging
I use one script in a package.json to build the Vue app into a folder ('dist' folder inside my backend folder) that is a collection of static files and start the backend server. My backend is set up to know when I want to go into staging mode and then serve the static files in the 'dist' folder.
Production
I use one script in a package.json to build the Vue app into a folder ('dist' folder inside my backend folder) that is a collection of static files and push my backend (incl. the built static files) to Heroku.
Well if you need to run both on the same port you could first build your app so that you receive a dist directory or whatever your output directory is named and set up an express server that serves that app and otherwise handles your api requests
const express = require("express");
const path = __dirname + '/app/views/';
const app = express();
app.use(express.static(path));
app.get('/', function (req,res) {
res.sendFile(path + "index.html");
});
app.get('/api', function (req,res) {
// your api handler
}
app.listen(8080)
Assuming that node and the 'app' will always run on the same server you can just use a template library like ejs.
You would then just bundle the app and api together, assuming that the front-end is tied to the backend, realistically you would not even need to hit the API as you could just return the records as part of the view, however if dynamic elements are needed you could still hit the API.
Now, with that said, if the API is something used by many applications then it would probably make sense to build that out as its own microservice, running on its own server and your frontend would be on its own. This way you have separation of concerns with the API and Vue app.

Node.js (MERN) Serving static files and API's

Im currently building a simple app using the MERN stack to learn. What Ive done so far:
-User Registration and Login (API)
-TODO List (API and Frontend with static files)
Ok, Everything works good and as expected, except for one thing.
I attempt to use my API's for any request, but at the same time I want my whole app to work rendering in a web browser (TODO list). So, the process I've followed is:
-Start node instance
-npm run build (To build react project files)
I did a research on how to use React build in node project and I did the following:
app.use(express.static('myproject/build'));
app.get('/*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'myproject', 'index.html'));
});
Good! So when I go to http://localhost:4000, it renders my index file and it actually works with my Login API, but I have some API's that are only available for consuming data and not rendering UI.
So, my problem is that when running the app, if I go to: http://localhost:4000/api/users/getdata
I get an error saying: Error: ENOENT: no such file or directory pointing the index.html
Ok, if I uncomment the code I posted before, then of course my app is not rendering UI, but my routes from API's work normally.
I know this might be setup/configuration process, but Im trying my best to understand this. If somebody could assist me with this problem please.
The hierarchy im working goes as follows:
myproject (contains models, routes (API's), middlewares, index.js)
frontend (inside folder 'myproject') (build, src (Components))
I did what #MaxAlex suggested. Changed the code from:
app.get('/*')
to
app.get('/')

NodeJS/Express proper handle of React builds

I would like to know how to properly handle React builds in a Express app. One major issue that I'm having right now is that, when using a browser the React app is always prioritized over other endpoints, even though it is at the end of the express chain.
I add the React build as static folder like so:
app.use(express.static(path.join(__dirname, '/react'), {
etag: false
}));
Also tried to set etag to false so it wouldn't cache the app, but it still opens even when the server is down. I send the react build by using a wildcard GET request at the end of the express chain:
app.get('*', (req, res) => {
res.sendFile(path.join(global.appRoot, '/views/index.html'));
});
There are other endpoints before it, but non of them are accessed through the browser, it just skips to the React build. The endpoints are called fine if Postman is used.
If it still opens when the server is down you have most likely a service-worker installed that is caching URLs with the index.html.
On Chrome, go to Applications tab of the Chrome Dev Tools and check if there are any service workers registered for the domain name you are using.
If there is you can unregister and then try again :)
But make sure that you check your service-worker, because if it is set to intercept all network requests with the html it will happen again.

Authentication with separated client and server

I have created web application in angular 2 for the client side and NodeJS with express framework for the server-side.
For the client side, I generated the project with angular-cli which runs on a lite-server, so my client and server are running on 2 different services.
I want to integrate ADFS authentication.
To implement the authentication, I am using passport-saml package.
There is a middleware in my server-side API which requires a user to authenticate.
The authentication process works fine when reaching the server directly (without the client).
However, I fail to access the API through the #angular/http.
I tried a simple GET request from the server (with the middleware in the end-point) and as a result I am getting the authentication page without redirection (I don't think it's the core problem, but the actual implementation is).
How should I properly implement the authentication when using separate services to the server and the client?
Hi I was also facing the same issue my angular project was hosted on 4200 port. and my node on 3000 port.
Its difficult to implement passport when we have 2 ports running.
step 1
make angular project static by doing an ng build to public folder .
make sure angular-cli.json has *"outDir": "../public",*
step 2
Now we can use the same node port to access angular roots please add below code in your node app.js
var path = require('path');
// Set Static Folder
app.use(express.static(path.join(__dirname, 'public')));
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'public/index.html'));
});
This will enable your angular project accessible through node port.
step 3:
Now we can add the passport login button in the UI
and give the url twitter
THIS IS not explanatory means feel to ask me the doubts.
You are not giving much detail on what the failed response was when the client did the GET to the server. Was it a 400? 401? 404? 500? did you get a CORS error?
Why are you using GET for your login endpoint. You should be POSTing credentials right in the POST payload?
Anyway, in your angular2 code you should have an auth service with a login method. The login method should do something like ..
login(credentials) {
return http.post(your_server_url, payload);
}
you can then subscribe to the Observable returned by the login method and if all is good router.navigate to the home page or if it did not go well display login error messages.
Set separate urls for ui and server in ..src/environments/environment.ts
export const environment = {
production: false,
BASE_URL: 'http://localhost:4200',
API_BASE_URL: 'http://localhost:5000',
};
In node app.js
app.use('/', express.static('public'));
app.get('*', function (req, res) {
res.sendFile(path.join(process.cwd(), 'public', 'index.html'));
});
In your development environment, you should accomplish this by instructing your Angular CLI to proxy all requests to a backend server running on a separate port whenever you run "ng serve". Angular has good documentation on how to do this at https://angular.io/guide/build#proxying-to-a-backend-server. This will make your development process much faster, because you will not need to manually build your Angular application to test every code change.
When a production build is run, Angular will move the distributed files to the location specified in the "outputPath" property of Angular.json (by default set to "dist/<projectName>"). Your main production server will serve them appropriately from there as mentioned by #Lijo in their answer.

Webpack somehow bypasses Express routing completely

I am starting from this excellent tutorial: https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/ and trying to extend it by adding a new page to serve through a new route. However after hours of mucking around I am realizing that somehow create-react-app is doing some weird magic (as mentioned in their docs here):
`create-react-app` configures a Webpack development server to run on `localhost:3000`.
This development server will bundle all static assets located under `client/src/`.
All requests to `localhost:3000` will serve `client/index.html` which will include Webpack's `bundle.js`.
The key quote is "All requests to localhost:3000 will serve client/index.html". I have no idea how this happens. So even though i mess around with routes/index.js:
app.route('/')
.get(function (req, res) {
res.sendFile(bipath.join(__dirname, '../public', 'THISCANBEANYRANDOMFILENAME.html'))
});
it doesnt matter because webpack is somehow directing localhost:3000 to index.html anyway. where and how is it doing this? Bottom line I am trying to modify my routes to serve a new html file and am running into all sorts of filepath issues (yes, even when i use require('path') or sendFile(...,{root: __dirname}).)
So what exactly is going on here and can you give me any hints to help me out?
Edit: this could be from babel as well as webpack - i'm not exactly clear where babel hands off and where webpack starts.
I haven't played around with create-react-app, but it seems like instead of using the default npm start, you could create your own server file and run that.
This looks like a good example.
https://medium.com/#patriciolpezjuri/using-create-react-app-with-react-router-express-js-8fa658bf892d#.6y4rrl61q
Alternatively, if you're looking to have routes used as an api, you could proxy them to a different port like shown in the tutorial you linked.

Resources