What is the best way to get a Vue app to correctly read the env variables that have been set on Heroku? - node.js

I have a vue app that is being hosted on Heroku. Now, because heroku doesn't do static apps, I wrote a simple express server to serve this app
//server.js
const express = require('express')
const serveStatic = require('serve-static')
const path = require('path')
app = express()
app.use((req, res, next) => {
if (process.env.NODE_ENV === 'production') {
req.headers['x-forwarded-proto'] !== 'https'
? res.redirect(301, 'https://' + req.hostname + req.originalUrl)
: next()
} else {
next()
}
})
app.use(serveStatic(path.join(__dirname, 'dist')))
const port = process.env.PORT || 80
app.listen(port)
And hooked the build process of the app to Heroku existing workflow through package.json
.
.
.
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"postinstall": "if test \"$NODE_ENV\" = \"production\" ; then yarn run build ; fi",
"start": "node server.js"
},
.
.
.
This whole thing works and technically so do the environment variables. However, I see that some env variables that I set on heroku (in their dashboards) yesterday are still undefined when I access them in vue using process.env.VUE_APP_XXX.
In development I use .env and that works as intended. My idea here is that because there's the express server in the middle things don't work as I would have expected.
Is there any way to make this thing work smoothly or am I better off using a static site hosting a-la Netflify?

Using ENV variables in Vue CLI (or Webpack build app in general) is done using Webpack DefinePlugin which replaces the values like process.env.VUE_APP_XXX in your code at build time
So if you define (or change) your variables directly in Heroku dashboard instead of .env file committed to repo, you need to rebuild you app on Heroku to pick up the changes...
If you are using ENV to configure any secrets (API keys etc), don't !! These values will be included directly in JS bundle of your app and easily accessible to anyone...

You can use heroku cli to set your env variables - documentation
$ heroku config:set DATABASE_URI=database_uri_here
$ heroku config:set SESSION_SECRET=session_secret
... and so on for each variable,
OR
if you have the env variables set in your .env file then you can do this as well
heroku config:set $(cat .env | sed '/^$/d; /#[[:print:]]*$/d')

Related

How to deploy Angular Universal app to Node.js production server?

I have an Angular 8 application with Universal that I want to deploy to a shared web host production server. I checked with the web host in advance and they told me that hosting an angular universal web app is possible on their shared web hosting. However, whatever I do, I can't get the website to work. When I go to the website I keep seeing the message: "This site can't be reached"
Things I have done so far:
Build project with npm run build:ssr which created a dist folder with a browser and server folder and a server.js file
Move the dist folder to the server inside the public_html folder.
Then accessed the server through SSH and did these:
install Node.js and npm
npm install
npm install pm2 -g
pm2 start dist/server.js
pm2 starts without problems.
These are some of the project files. If any are missing please ask and I'll add them to the question.
Part of the package.json with the scripts:
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"compile:server": "webpack --config webpack.server.config.js --progress --colors",
"serve:ssr": "node dist/server",
"build:ssr": "npm run build:client-and-server-bundles && npm run compile:server",
"build:client-and-server-bundles": "ng build --prod && ng run ProjectName:server:production --bundleDependencies all"
},
server.js from the build (only the express part, since it's 25000+ lines):
const app = express__WEBPACK_IMPORTED_MODULE_1__();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(process.cwd(), 'dist/browser');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap } = __webpack_require__(144);
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', DIST_FOLDER);
// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Serve static files from /browser
app.get('*.*', express__WEBPACK_IMPORTED_MODULE_1__["static"](DIST_FOLDER, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
According to several answers on SO or elsewhere you "simply" copy paste the dist folder to the server, run pm2 and your website supposedly works. I feel like there's a lot missing to get it working though.
Does someone know how to properly deploy an Angular Universal website to a production server?
I ended up using a tool in my DirectAdmin console called 'NodeJS Selector', because PM2 kept causing problems for me. It's a tool to install your application, do npm install and start the app. So basically what you'd do using SSH, but in a fancy UI. The setup looks like this:
With my folder structure looking like this:
domains
- appname.com
- public_html
- browser (=> this set as the document root in Apache, because index is located here)
- index.html
- .htaccess
- other files...
- server
- server.js
- package.json
I had my web host do 2 things, because I'm not a root user on the server. They set the documentroot to the browser folder in Apache's httpd.conf file. In the same file they also added proxy settings to port 4000 because that's where my app is running on. So in the httpd.conf file will be something like this:
DocumentRoot "/domains/appname.com/public_html/browser"
<Proxy *>
Order allow,deny
Allow from all
</Proxy>
ProxyPreserveHost On
ProxyRequests Off
ProxyPass / https://localhost:4000/
ProxyPassReverse / https://localhost:4000/
Next in the NodeJS selector you set the root to where the startup file is located (in my case server.js). The package.json needs to be in de public_html folder as well so you can do npm install in the Selector.
To start the app you click 'Run JS script' and choose the serve:ssr option and it runs the node server command.
The Angular docs describe how you have to rewrite some rules on the server. Apache is installed on my web server so I added an .htaccess file with the rewrite rules from the docs. However this app is served by Node and Node can serve the actual paths. So in my case I didn't have to add the .htaccess file with rewrite rules. On the contrary, when I added the .htaccess file it caused the Universal side of my app not to fully render. See this question for more info.
In server.ts file change
const DIST_FOLDER = join(process.cwd(), 'dist/browser');
to
const DIST_FOLDER = join(process.cwd(), 'browser');
Now build the application by npm run build:ssr and copy everything inside of dist folder to your public folder (on host). Remember, instead of pm2 start dist/server.js, you should run pm2 start server.js.

express server starting react client

Until now, I have been using create-react-app for my projects, with the express-server and the react client each in their own folders.
However, I am now trying to avoid create-react-app in order to really understand how everything work under the hood. I am reading an Hacker Noon article that explains how to setup react with typescript and webpack. In this article they also have the express server at the root of the client which compiles everything itself:
const path = require('path'),
express = require('express'),
webpack = require('webpack'),
webpackConfig = require('./webpack.config.js'),
app = express(),
port = process.env.PORT || 3000;
app.listen(port, () => { console.log(`App is listening on port ${port}`) });
app.get('/', (req, res) => {
res.sendFile(path.resolve(__dirname, 'dist', 'index.html'));
});
let compiler = webpack(webpackConfig);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true, publicPath: webpackConfig.output.publicPath, stats: { colors: true }
}));
app.use(require('webpack-hot-middleware')(compiler));
app.use(express.static(path.resolve(__dirname, 'dist')));
In the end, the start command looks like it:
"start": "npm run build && node server.js"
So I assume the client and the server start on the same port.
Why would you do such a thing? Are there any pros and cons?
It is true that this will allow your development to happen using the same server as express and that web pack will continuously update your dist/index.html file with whatever updates you make to your file. There's not too much of a disadvantage to this as this is just for development. But typically on prod you'll have a single built file that you will serve. And it will not web pack-dev-middleware to be running. Once you've built your server. For the purposes of production it might be possible that you'll only need static assets. But typically, even the server which serves mostly client files will potentially need a server if you want to do server side rendering and/or code splitting.
The command: "npm run build && node server.js" will run the bash/cmd commands into the terminal. npm run build is one step because of the use of && it will if that command succeeds, run the next command which is node server.js which is a strange command I would probably run node ./ (and put the server as index.js) or at least just write node server.
What I'd prefer to see in your package.json:
"start": "yarn build && node ./"
That would be possible if you mv server.js index.js (and npm i -g yarn).
Another thing to note, and look into is what the build step does.
Further Explanation:
The command runs the build step so check what your "build": key runs in your package.json.
This command will probably not exit with the code 1 (any exit code of a terminal process that is above 0 will result in an error and will not pass the &&).
Presumably, the build process described in the package.json will take all the javascript and CSS files and put them into the index.html file which will then be sent to the client side whenever someone access the '/' path.
After that succeeds, it will start the server that you put the code to above.
res.sendFile(path.resolve(__dirname, 'dist', 'index.html'));
will happen if anybody comes across the '/' path.

How to set up dev and API server from create-react-app?

I've started a new app with create-react-app, and ejected from that. I made a small express server as follows:
const express = require('express');
const app = express();
if(process.env.NODE_ENV === 'production') {
app.use(express.static('build'));
}
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server started at: http://localhost:${port}/`);
});
In package.json I've added a line, "proxy": http://localhost:3000", as well as switching the commands:
"scripts": {
"run": "npm-run-all -p watch-css start-js",
"start": "node server.js",
},
run used to be start.
However now of course when I run npm start and go to localhost:3000, I get Cannot GET /. I need this server to receive and return local API calls I'll be making from my app, but I also want it to run a hot-reloading dev server just like the old npm start (now npm run) command did. How do I do this?
Some time ago I made a fork of the create-react-app repository adding webpack watch option because of this same reason. It might help you.
Just to add more info, I really invested time looking on how to get webpackdevserver to build the "bundle.js", and found that it is not possible because it loads the bundle into memory but doesn't persist it, so the file is never created. The only way available is the webpack watch option but, I don't understand why the create-react-app team can't add it to the repo, it's a really requested feature, and there are more forks than mine that solves this issue. So, you have three options:
Use the proxy server in package.json (if it works)
Make your own fork and add the watch option, or use an existing one
Don't use create-react-app

Why Angular 2 doesn't load default app-root component?

I'm trying to start Angular app generated via angular CLI, but it seems like the default app-root component doesn't load. Need to say, that I'm using proxy for connection between angular app and express server, and I'm running two scripts at the same time: node bin/www for express/node.js start and ng serve --proxy-config proxy.config.json for starting Angular and creating proxy connection, it looks like this (the part of package.json):
"scripts": {
"start": "concurrently --kill-others \"node bin/www\" \"ng serve --proxy-config proxy.config.json\""
}
The index page loads fine, but it seems that app-root component (the default component, which was created from angular CLI ng new) doesn't loading:
Here is my node.js/express uses and a route:
var express = require('express');
var router = express.Router();
var app = express();
var path = require('path');
app.use(express.static('./src/client/'));
app.use(express.static('./'));
app.use(express.static('./tmp'));
app.use('/*', express.static(path.resolve('src/client/index.html')));
router.get('*', function(req, res) {
res.sendFile(path.resolve('src/client/index.html'));
});
module.exports = router;
And the structure of my project (if needed):
What did I miss? Why the default app-root component doesn't loading? (need to say, when I run ng serve, it starts the angular homepage as needed and the component is OK, so I think the problem is somewhere in express).
Thanks in advance
You should serve the contents of the dist/ folder after calling ng build --prod (the --prod is important, as the default is --dev). So, it would be something like this:
"scripts": {
"start": "ng build --prod && node bin/www"
}
And, more or less adapting your express script:
app.use(express.static('./dist'));
app.use('/*', express.static(path.resolve('dist/index.html')));
router.get('*', function(req, res) {
res.sendFile(path.resolve('dist/index.html'));
});

Best way to set a Node/Express app to live or production mode

I'm currently in the process of making my node/express app into a production deployment, and as part of this, I need to make it run in a production friendly mode (e.g. fewer debugs to stdOut, write logs to different places, tell users less when errors occur etc.).
I'm struggling a bit with this, as whenever I set a variable of virtually any kind to invoke a 'production' mode, it doesn't take affect for the program as it runs.
When launched in dev mode, my code runs through Gulp, and runs this script:
#!/usr/bin/env node
var debug = require('debug')('ascema');
var app = require('../app');
app.set('port', process.env.PORT || 3000);
var server = app.listen(app.get('port'), function() {
debug('Express server listening on port ' + server.address().port);
});
Which, as you know, is just the generated launch script from the express generator.
In order to run it in live mode, I created an alternative startup for the server to use (I could hardly use gulp anyway) and live.js runs this:
#!/usr/bin/env node
var app = require('./app.js');
app.set('env', 'production');
console.log('running on Port 3000');
var server = app.listen(3000);
But, when I use app.get('env') anywhere in the app (e.g. in app.js or in it's various dependencies) it still returns 'development' and so none of my production tasks happen.
What am I doing wrong here?
Many thanks.
You have to set the NODE_ENV variable on the command line when you execute your NodeJS application.
For example:
NODE_ENV=production node app.js
Also, NODE_ENV is an environment variable so if set it in the environment on your server, you will not need to provide it every time you execute your application so node app.js will do.
You can set your environment variables in the /etc/environment file. Here are more details on that.
By using NPM you might to be used the follow scripts in the package.json:
"scripts": {
"start": "nodemon ./bin/www",
"dev_win": "set NODE_ENV=development && node ./bin/www >> /romba/log/api.log 2>> /romba/log/error.log",
"prod_win": "set NODE_ENV=production && node ./bin/www >> /romba/log/api.log 2>> /romba/log/error.log"
"prod_nix": "NODE_ENV=production node ./bin/www >> /romba/log/api.log 2>> /romba/log/_error.log"
},...
To start one of the script use the command:
npm run-script prod_win
In the JavaScript code I check the condition:
process.env.NODE_ENV.indexOf('production') > -1
or with Express.js
app.use((err, req, res, next) => {
req.app.get('env') === 'development' ? ...

Resources