How to pass config variables from server (Express) to React App - node.js

I am building React + Express app and I need to pass some config variables from server config into React App (for example API URL).
I need to do so in development (2 servers - Webpack Dev server + Node.js) and also in production (Only Node.js Express server with built frontend).
How to achieve this?
I tried to pass locals from Express to my template where I did window.config = '<%- JSON.stringify(config) %>' (EJS template system) and then used window.config in React App. I does not think this is right approach.

React is client side and no one will suggest you to pass your server config variables like api keys to client side but if you want to pass the config variables to client side.
You can make do it as
// import config variable to express app.js server side
const config = require('./config.js');
// send the config variable
app.get('/getconfig', (req, res) => {
res.json(config);
});
In the client side make axios get request to /getconfig in actions creator file
export function getConfig() {
const req = axios.get('/getconfig')
.then(res=> res.data)
.catch(err => console.log(err));
return {
type: "GETCONFIG",
payload: req
}
}
Now you can add it to reducers switch case and then use it in any react component.

You can consider to use DefinePlugin in webpack. This feature allow you to create global constants being used in the front-end logic. And as it is created at compile time, you can retrieve the config from Node layer.
For example, you have 2 GTM containers, one for development, another for production. And in the production webpack config, we can use some like this:
const config = require('./node/config/prod.js');
module.exports = {
// skip other setting...
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV' : JSON.stringify('production'),
'process.env.GTM_ID' : JSON.stringify(config.gtm.id)
});
]
};
Then in the html you can use process.env.GTM_ID to dynamically get the GTM id among the different environments.

Related

How to make API request from react client to express server running on the Heroku platform

Ive been trying to deploy a Twitch like application using react, redux, node media server and json server module to Heroku. However, I keep running into a issue when trying to connect my react client and express server via a api request, during production.
Im trying to make the actual request through my action creators and by using axios with a base url of http://localhost:4000, however that only works on my local machine.
const response = await streams.get("/streams");
dispatch({ type: FETCH_STREAMS, payload: response.data });
};
You can view my full repo at https://github.com/XorinNebulas/Streamy
You can also view my current deployed version of the site on Heroku at
https://streamy-app.herokuapp.com/
here is my api/server.js file. My express server will be watching on a random port equal to process.env.PORT, so I have no way of knowing how to make a network request via my action creators to that random port during production.
const path = require("path");
const cors = require("cors");
const jsonServer = require("json-server");
const server = jsonServer.create();
const router = jsonServer.router("db.json");
const middlewares = jsonServer.defaults({
static: "../client/build"
});
const PORT = process.env.PORT || 4000;
// Set default middlewares (logger, static, cors and no-cache)
server.use(cors());
server.use(middlewares);
if (process.env.NODE_ENV === "production") {
// Add custom routes before JSON Server router
server.get("*", (req, res) => {
res.sendFile(
path.resolve(__dirname, "../", "client", "build", "index.html")
);
});
}
// Use default router
server.use(router);
server.listen(PORT, () => {
console.log(`JSON Server is listening on port ${PORT}`);
});
I expected the request to go thru and load up some data from api/db.json, with a resquest url of https://streamy-app.herokuapp.com/streams but instead i got a request url of http://localhost:4000/streams, which of course leads to the CORS issue below
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:4000/streams. (Reason: CORS request did not succeed).
I would truly appreciate any suggestions, been working on this for days.
Alright looks like I figured it out. I simply went into streams/client/package.json and added
"proxy":"http://localhost:4000"
I then went into streams\client\src and deleted the api folder which contained my custom axios with a base url. using instead axios out of the box for my action creators
const response = await axios.get("/streams");
dispatch({ type: FETCH_STREAMS, payload: response.data });
};
Now while running locally in development mode, I'm able to make a request to http://localhost:4000/streams, but after deploying my node app to Heroku I successfully make a request over to https://streamy-app.herokuapp.com/streams
hope this helps someone with slimier issues.
First, you should know that Heroku doesn't allow to expose multiple ports, which means you should change the approach of multiple ports to something else (see this answer).
Second, the file client/src/apis/streams.js is hard-coded configured to send requests to http://localhost:4000/ - which is not a good idea.
Whatever approach you choose - even deploying to another host server - you will need to dynamically configure the API endpoint, per environment.
I would also recommend you to:
Change the way you deploy react, as explained here.
After doing the above, consider consolidating your API service with the static server, so that you don't need multiple ports, and then everything becomes easier.

How do I add vue.js client to an existing node.js api?

I have a node.js API with several endpoints that is working great. I have created a separate vue.js client app to manipulate data with the API I created. This is also working great. I have run into an issue where I need these apps to be running on the same port so a third party proxy system can access both these apps simultaneously.
I have seen a way to do this by copying the dist directory over to the node server but since I need this for a production env I need an alternative that dynamically builds the front end when building the node API as well.
Is there a production level alternative to adding a client app to run on an existing node server? Any recommendations or help would be greatly appreciated!
Update: I've added this to my router file and have client and server in one project
const path = require('path');
const router = express.Router();
router.use(express.static(path.join(__dirname, '../client/dist')));
//production mode
if(process.env.NODE_ENV === 'production') {
router.use(express.static(path.join(__dirname, '../client/dist')));
//
router.get('/test', (req, res) => {
res.sendFile(path.join(__dirname = '../client/dist/index.html'));
})
}
//build mode
router.get('/test', (req, res) => {
res.sendFile(path.join(__dirname+'../client/dist/index.html'));
})
You can expose the dist directory built with vue-cli using a express static route. This directory should have your built assets along with index.html
app.use('/app', express.static(path.join(__dirname, '../client/dist'));
However, now since your application is not at the root directory you have to add a base url to your vue.config.js. Rebuild the app after this.
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/app/' : '',
...
...
...
}
Now your vue app should be available at
/app/index.html

Node.js express: access application config from required modules

I've got a large Node.js Express API to build, so I want to make sure my solution architecture is stable and scalable.
The routes are defined each in its own separate file and stored in /routes folder. There's an index.js file as well, where all of the child routes are registered to the master router.
There's an application configuration file /config/app.js:
module.exports = {
development: {
configVar: 123
},
test: {
configVar: 456
},
production: {
configVar: 789
},
}
The config object is loaded in the main application index.js file:
const path = require("path");
const env = process.env.NODE_ENV || "development";
const config = require(path.join(__dirname, 'config', 'app.js'))[env];
Now that I have the config object, I'd like to pass it down to whatever consumer. For Express app it's mostly the routes. Loading the config repeatedly in each module would be kind of redundant. So I have set up my main routes module (/routes/index.js) as follows:
const express = require('express');
const router = express.Router();
module.exports = {
init: function(config) {
router.use('/test', require('./test').init(config));
return router;
}
}
And the /test route (/routes/test.js):
const express = require('express');
const router = express.Router();
module.exports = {
init: function(config) {
router.post("/", function(req, res) {
res.send('hello world');
});
return router;
}
}
I also like that with this structure I can mock the config object when testing the routes. My question is, if this can be considered a good pattern for Express application, or perhaps there is some convention to follow.
There are many ways to skin a cat and many more ways to setup an express project.
The one thing that jumps out at me as a good thing to change is your configuration method. Your approach makes total sense, but there's a module called config which works in much the same way you've illustrated but you won't need to list every option for every environment.
Using this module you can have a default.json file which contains all of your base configurations. You can then override it with a file which matches the name of your environment such as development or test without having to do it by hand.
More importantly, it will also let you map from your applications configurations to Environment Variables. You very rarely want to store your applications configuration values in the codebase itself, especially when we're talking API keys and secrets. The last thing you want to do is commit API keys / secrets into version control. The config module linked above will allow you to define environment variable mappings which means you can feed them in via the systems environment variables instead.
I should note finally that there are many modules which act in a similar way to config and they all function slightly differently. You might also want to consider nconf which is also a brilliant module.

Heroku node + react app: anchor tag auth request unhandled

I'm building a node + express server, with create-react-app to the frontend.
I used passportjs for auth routes handling, and all the stuff totally working on localhost ( backend on port 5000 and frontend on port 3000, with a proxy ).
When I deploy to Heroku, seems like the server can't recognize my auth routes and so heroku serve up static index.html.
If I test my APIs with Postman all seems to work ( I can see the html page for google oauth ), but with an anchor tag in my react app or manually writing the endpoint in the url, I can see only the same page reloading.
My server index.js:
const express = require('express')
const passport = require('passport')
const mongoose = require('mongoose')
const path = require('path')
// KEYS
const keys = require('./config/keys')
// MONGOOSE MODELS
require('./models/User')
mongoose.connect(keys.mongoURI)
// PASSPORT SETUP
require('./config/passport')
// CREATE THE SERVER
const app = express()
// EXTERNAL MIDDLEWARES
require('./middlewares/external')(app)
// ROUTE HANDLERS
require('./routes/authRoutes')(app)
// PRODUCTION SETUP
if (process.env.NODE_ENV === 'production') {
// express serve up production assets ( main.js, main.css )
app.use(express.static('client/build'))
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'))
})
}
// START THE SERVER
const PORT = process.env.PORT || 5000
app.listen(PORT)
Flow:
LOCALHOST:
react app starts -> I click 'Google Login' -> GET request to "/auth/google" -> google oauth flow -> redirect to "/" and my react app reappears, the user is logged in.
HEROKU:
react app on my-app.herokuapp.com/ -> click on "Google Login" -> the page reloads, nothing happens. the user is not logged in.
Please guys, help me.
Thanks
This is a result of the service worker being installed by default to make your app a Progressive Web App
To determine if this is an issue for you, test your heroku production mode app in incognito mode. The request for /auth/google should now reach the server and behave as it does in development.
Once you determine it is an issue, you can remove the
import registerServiceWorker from "./registerServiceWorker";
from your /client/src/index.js file.
You browser cache may already contain an installed service worker so you may have to
clear browser cache on a user browsers
uninstall the server worker programmatically
import { unregister } from './registerServiceWorker';
....
unregister();
I had the same issues with same symptoms exactly.
For me the cause was a typo in the keys: in server/config/prod.js I had a line reading cookieKey: process.env.COOKIE_KEY but in Heroku Config Variables that variable was named cookieKey. Renaming it to COOKIE_KEY inside Heroku solved the issue.
If you've followed the Stephen Grider tutorial one thing I'm wondering: Is your passport.js file in config or services? I see you've written in index.js: require('./config/passport')
whereas mine in index.js is require('./services/passport')
may not be your solution to the google oauth flow hanging in production but may help.

POST request with updating json file using create-react-app

I'm making a project using create-react-app. There is a configured server and so on. I'm using react-router-dom for routing in my app. There is 'Comments' component. When it starts render itself it goes to my local json file and takes comments from there using ajax. When user clicks 'submit' It sends POST request with form's fields to the same json file. I have code for adding a new object to my json file. It should work when user in '/api/comments' route . This is the code for adding a new object to my json file (requires express):
`app.post('/api/comments', function(req, res) {
fs.readFile(COMMENTS_FILE, function(err, data) {
if (err) {
console.error(err);
process.exit(1);
}
var comments = JSON.parse(data);
var newComment = {
id: Date.now(),
author: req.body.author,
text: req.body.text,
};
comments.push(newComment);
fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4),
function(err) {
if (err) {
console.error(err);
process.exit(1);
}
res.json(comments);
});
});
});`
But I don't know where I shoud put this code if I'm using 'create-react-app' and it uses it's own configured server (as far I know). Maybe there is a way to change server which 'create-react-app' uses and put there this code to handle this route? Or maybe there is a way to handle this route using 'react-router'?
If I understand your question correctly the code you have posted here is server side code. The app you have made using create-react-app is a front end application and therefore does not have any server side code. You could however host a second server that would expose the api routes you need and then call into that server using a http library like axios.
I'm using a create-react-app and express as my api server. Setting up express to run alongside webpack-dev-server is a supported feature of create-react-app.
I use npm-run-all to fire-up both the client and proxy express api server in my start-up script defined in package.json. Here is what I believe is all that I needed to do:
In my webpack.config.dev.json file I defined a proxy setting in the devServer json block. Specifically:
proxy: { "/api": "http://localhost:3001" },
In my package.json file I configured a start script that uses npm-run-all to fire up both the react-app and express simultaneously.
I use server.js to fire-up express; this is where I store the equivalent of the code you outlined in your question.

Resources