How to inject API server URL when deploying react frontend? - reactjs

Disclaimer: I am a React noob so perhaps what I am trying to do is not the React Way
I am writing a React front-end that will be deployed to be served statically by some cloud provider, e.g. S3 or Google Storage or whatever. This front-end interacts with several API servers that live somewhere in the cloud, maybe in the same provider, maybe not. Furthermore, while developing the UI or part of it, those servers' addresses might be local or testing instances.
How do I inject the API server(s) URLs into my react application in a flexible so that I can deploy in dev, staging or prod using different addresses?
SOLUTION: I finally ended up using a combination of solutions proposed:
use .env.production and .env.development files (exact names) to store the variable REACT_APP_API_URI = 'host'
this is automatically picked-up by create-react-app's build scaffolding and available in UI code as process.env.REACT_APP_API_URI
Note this somewhat goes against principles from 12 Factor Apps, e.g. storing env variables in files in version control but it does the job ATM.

You can try this:
// http.js
const getBaseUrl = () => {
let url;
switch(process.env.NODE_ENV) {
case 'production':
url = 'https://stackoverflow.com';
break;
case 'development':
default:
url = 'https://google.com';
}
return url;
}
export default axios.create({
baseURL: getBaseUrl(),
});

Using this package https://github.com/toddbluhm/env-cmd you could create an env file for your environment
for example create .env.staging and .env file with this code
// .env.staging file
API_URL=https://staging.url.com/api/
// .env file
API_URL=https://url.com/api/
How to fetch with API_URL from env variable:
fetch(process.env.API_URL)
Then you can just add some extra scripts in your package.json:
{
"scripts": {
"build:staging": "env-cmd .env.staging yarn build",
"build:prod": "env-cmd .env yarn build"
}
}

You can use .env file if the API's are constant for development or production environment. after build you can't change these parameters.
If you want to change the URL after build, add a js file lets say config.js
Include the conf.js in index.html
Add URL in conf.js like
var URL1 = 'https://www.google.com'
You can access the parameter like :
export const {URL1} = window;

You can do that making use of environment variables on the build step for example.
You can use something like .env that allows you to define environment variables and load them on your webpack file for example (assuming you use webpack). But you can really use it with any bundler.
.env file:
API=http://localhost:3000
On your webpack you could make use of the DefinePlugin
example taken from docs: add your API env
...
require('dotenv').config()
...
new webpack.DefinePlugin({
API_ENDPOINT: process.env.API,
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify('5fa3b9'),
BROWSER_SUPPORTS_HTML5: true,
TWO: '1+1',
'typeof window': JSON.stringify('object')
});
Anyway, this is just one way. I like this way because it makes my project ready for defining API keys and other useful stuff for different environments.
NOTE: You can even define different .env files for local, staging and production and load the respective one in the webpack depending on the build type.

Related

Nextjs api "pages/api" doesn't work on vercel server

Hello guys may you help me?
I'm trying to configure my fake API to create some personal projects but in my case, the method using the /pages/api folder only works for me in localhost when I deploy to the server on Vercel the project can't find my endpoints.
In my case I'm using the src/ folder method to develop my app and I don't know if this structure can cause problems with api folder.
One thing that I tried and worked is deploying to vercel using the api folder at the root of the application (out of /src folder) but the api stop working on localhost.
This structure works on localhost but doesn't work on server:
├───public/
├───src/
├───api/
├───pages/
...
next.config.js
package.json
This structure works on server but doesn't work on localhost:
├───api/
├───public/
├───src/
├───pages/
...
next.config.js
package.json
This is the method that I'm using to get data:
AXIOS API:
import axios from 'axios'
const api = axios.create({
baseURL: '/api/'
})
export default api
SWR HOOK:
import api from 'src/services/api'
import useSWR from 'swr'
function SwrFetchHook<Data = any, Error = any>(url: string) {
const { data, error } = useSWR<Data, Error>(url, async url => {
const response = await api.get(url)
return response.data
})
return { data, error }
}
export default SwrFetchHook
SWR callback:
const { data } = SwrFetchHook<INavItem[]>('categories')
I hope that I could explain, my question here is how is the best way to work with this feature because the /src folder is common to use with nextjs but I don't know if this is the real problem with the api folder.
Thanks!
Not 100% sure if this is the same issue. I had this warning in my build phase:
warn - Statically exporting a Next.js application via `next export` disables API routes. This command is meant for static-only hosts, and is not necessary to make your application static.
Make sure you are using the correct build command in out package.json scripts.
I'm my case:
"next build && next export" needed to be changed to "build": "next build"
Note: I removed && next export
This disabled the static export feature and allowed the use of pages/api when running yarn start note: yarn start relies on the build script within Vercel's build pipeline. So do not override the default settings unless it is needed.
Also normal Node/JS projects you can define a source folder in the scripts area ie "start": "<SOME_COMMAND> ./src"....but Next.js does this automatically so I do not think having an src file is the issue. I too have an src file in my project and it is a very common way (preferred way) of organizing your JS project. You shouldn't have to touch this if you are using next.
I tried deploying my app on digital ocean and it worked cuz vercel was not providing a server. I was not able to call my api from POSTMAN as well. Deployed it on digitalOcean and then it ran a server just like my localhost.

How can we pass environment or config variables during run time in a cra(createreactapp)

How can we pass environment or config variables during run time in a cra(createreactapp). I do not want to build for different env but use one build with different configs in different env
You can add 2 environment variable files to the root of your project for development and production versions. They should be named;
.env.development - This will use npm start (dev stage)
.env.production - This will use for the build (prod stage)
A property can be added as bellow, give attention to the prefix REACT_APP_
REACT_APP_PROPERTY1=some_value
And this property can be read as,
<p>{process.env.REACT_APP_PROPERTY1}</p>
We build our create-react apps as static websites and we push them directly to a simple web server. That's why you can't use env variables there. We found a nice workaround and we plan to write a short post about it:
1. Start your app with (one) environment variable
Let's assume you have a development, staging and production environment, like we do most of our projects.
We set just one ENV variable REACT_APP_ENV in our start up scripts. Each environment has it's own start and build script.
# package.json
# ...
"scripts": {
"start": "REACT_APP_ENV=development react-scripts start",
"start:staging": "REACT_APP_ENV=staging react-scripts start",
"start:prod": "REACT_APP_ENV=production react-scripts start",
"build:staging": "REACT_APP_ENV=staging react-scripts build",
"build:prod": "REACT_APP_ENV=production react-scripts build"
},
2. Setup config file
In your create-react app you store a config file under src/config/index.js. In this config file you can define the values based on the environment.
# src/config/index.js
const env = process.env.REACT_APP_ENV
export const appConfig = {
api: {
networkInterface: ({
development: 'http://localhost:5000/graphql',
staging: 'https://project-staging.herokuapp.com/graphql',
production: 'https://project.herokuapp.com/graphql',
})[env],
// add more here
},
}
export default appConfig
3. Usage
In the app you access the config simple like this:
import config from './src/config'
# usage
config.api.networkInterface
I had the same question a couple of days ago and found a hacky, yet a potential solution. Basically, you use a config.js inside your /build/public directory. The config.js could be a simple code like this:-
window.DYNAMIC_PROPERTIES = {
"ENV": "UAT",
"API_ENDPOINT": "UAT"
}
You want this particular file to be a dynamically generated one (based on your environment). If you are interested in how I achieved this, read on till the end.
Now, to access this in react application, include this script in your index.html (head tags).
<script src="%PUBLIC_URL%/configs.js"></script>
Once this is done, you should be able to access the env variable dynamically in your application like this.
const envValue = window.DYNAMIC_PROPERTIES.ENV
const apiEndpoint = window.DYNAMIC_PROPERTIES.API_ENDPOINT
The basic idea here is that the HTML will execute the script in your build/public folder. This script will attach properties to the window scope and make them available to your application.
Now the whole point of doing all this is to be able to dynamically inject the environment variables. Here's how I achieved it:-
We use an nodejs (express) server to serve the UI, so I managed to dynamically create this simple js file in the /build/public/ folder. Sample code for the server.js:-
const path = require("path");
const express = require("express");
const fs = require('fs');
const app = express(); // create express app
fs.copyFile(`config.${process.env.ENVIRONMENT}.js`, "./build/config.js", (err) => {
if(err){console.log("Something went wrong while setting env variables", err)}
else{console.log("Env variables set for ",process.env.ENVIRONMENT )}
});
// add middlewares
app.use(express.static(path.join(__dirname, ".", "build")));
app.use(express.static("public"));
app.use((req, res, next) => {
res.sendFile(path.join(__dirname, ".", "build", "index.html"));
});
// start express server on port 5000
app.listen(5000, () => {
console.log("server started on port 5000");
});
Basically, I have separate config files like config.dev.js, config.uat.js and so on. The start command for this server would inject a property "ENVIRONMENT" as either dev or uat or prod and this node server will place the correct config files in the build/public at application startup (notice the fs.copyFile command).
Ok, hope this helps. If you find a better way to do this, do comment your solution.

How to inject port and host using ENV variable in Create React App Proxy settings?

Is it possible to set proxy settings in CRA differently than with package.json? For example using ENV variables?
From CRA docs it states
Use environment variables to inject the right server host and port into your app.
Is this sentence about proxy or dev server itself?
According to the this and this, the only way to influence proxy settings is via package.json.
Note: this feature is available with react-scripts#2.0.0 and higher.
You can manually configure proxy now. You need to install http-proxy-middleware first.
npm install http-proxy-middleware --save
Then create a file src/setupProxy.js with following content.
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api', // You can pass in an array too eg. ['/api', '/another/path']
createProxyMiddleware({
target: process.env.REACT_APP_PROXY_HOST,
changeOrigin: true,
})
);
};
You can add proxy now in the .env file, remember you need the REACT_APP_ prefix for development server to pick up the environment variable.
REACT_APP_PROXY_HOST=http://localhost:5000
Ref: Proxy manually
Regarding:
Use environment variables to inject the right server host and port
into your app.
This refers to neither the proxy server nor the dev server. This is a recommendation on how to handle per-environment server settings if proxy does not provide enough flexibility for you (i.e. if you need to use protocols other than http, https, or ws). For example, if you have an api call within your code, you can use an environment variable to do something like this:
axios(process.env.REACT_APP_BASE_URL + endpoint, options).then(data=>{ console.dir(data); })
Where each environment has it's own definition of REACT_APP_BASE_URL.
Without package.json:
It is not possible to influence proxy settings without modifying package.json.
See Configuring the Proxy Manually (CRA docs)
package.json is the only documented way to influence proxy settings.
You can also see line 96 of CRA's start.js. This implementation leaves absolutely no ability to use ENV vars to inject the host/port to CRA's proxy settings.
This is really one of the reasons I detest CRA, to be honest. All of this behavior is so very easily handled when not bound by the limitations of CRA.
You could just eject create react app:
npm run eject
All of these problems will go away instantly... The start.js script referenced above is a bit of the code that is ejected. So you would very easily be able to replace those proxy settings with ENV vars.
Just be certain to create a checkpoint for yourself prior to doing this as there is no reverting an ejection.
However--if you must stick with CRA--you CAN very easily modify package.json using environment variables.
How to inject ENV vars into package.json at build time:
This is essentially what CRA does with the REACT_APP_ env vars (though it directly injects them into process.env).
package.json is just a big JSON object, so all you need to do is parse it on the server side right before you deploy, update the "proxy" value to the ones provided by your ENV vars, save the file, and then run your build/deploy.
Here is a very simple example of how to achieve this, you just need to run this script before deployment in a context where you still have access to process.env:
const fs = require('fs');
// read/process package.json
const file = './package.json';
let pkg = JSON.parse(fs.readFileSync(file).toString());
// at this point you should have access to your ENV vars
pkg.proxy = `${process.env.HOST}:${process.env.PORT}`;
// the 2 enables pretty-printing and defines the number of spaces to use
fs.writeFileSync(pkg, JSON.stringify(file, null, 2));
I know this doesn't explicitly answer the question of how to handle this "without package.json", but I don't think that's the necessary approach anyways.
Further, this DOES asnswer the title question of "How to inject port and host using ENV variable in Create React App Proxy settings?"
Is it possible to set proxy settings in CRA differently than with package.json?
No. It is not possible. Well, at least not yet (2017/01/14).
The React proxy functionality is getting a bit complicated and as a result, the idea of setting up a proxy in a React app from env variables will be implemented as soon the simplification of the "advanced" proxy configuration is finished.
Is this sentence about proxy or dev server itself?
It Is about the dev server itself. All proxy configuration, as things stand, is configured in package file.
You can define env variable for port and host.
REACT_APP_PORT: "PORT of Application",
REACT_APP_HOST: "host of application"
To inject all the env to your application you can use webpack define plugin.
You need to add in plugins section like.
const env = require("path to your env file")
//other webpack settings
plugins: {
//plugins
new webpack.DefinePlugin(env)
}
Now if you see the code of create-react-app it looks in env variable first to find the port and host.
Also, note that
You MUST create custom environment variables beginning with REACT_APP_. Any other variables except NODE_ENV will be ignored to avoid accidentally exposing a private key on the machine that could have the same name

How to parameterize variables in a React Webpack app for different environments?

I'm working on a React web app which was created by create-react-app and I am getting ready to deploy it.
The issue is, for development, the app uses the api instance running on my dev environment (localhost:{api-port}) but for the deployed app this should point to the server api instance (api.myapp.com).
Currently the host is just a variable in my networking component:
const hostname = 'localhost:9876'
I plan on using webpack to generate the static files which will be served by the production front-end, and I would like to continue developing using npm start as set up by create-react-app.
What would be the correct way to set up my project such that the host can be set automatically to the correct value based on whether I'm running the dev server or building for production?
A common solution is to check against process.env.NODE_ENV like this:
const hostname = process.env.NODE_ENV === "development" ? "localhost:9876" : "localhost:6789";
You may need to force the environment variable to be present in your Webpack configuration file using the DefinePlugin like this:
plugins: [
new webpack.DefinePlugin({
"process.env": {
NODE_ENV:JSON.stringify(process.env.NODE_ENV || "development")
}
})
]
An alternative solution might be to use the config package and provide your hostname string as a configuration parameter. config will inherit configurations from files based on the current NODE_ENV. It will start by using configuration from a default.json file, then override it with a development.json, or production.json, depending on your environment.
Note that you'll need for the config files to be copied to your output directory using CopyWebpackPlugin for it to work.
There are definitely many ways you could achieve that. One of those solutions would be to use webpacks's DefinePlugin. In your plugins section in webpack configuration you would add something like this:
new webpack.DefinePlugin({
API_HOST: process.env.NODE_ENV === 'production'
? JSON.stringify('localhost:8080')
: JSON.stringify('api.com')
}),
That creates a global variable API_HOST available everywhere in your codebase, which you can use. You can read more about the DefinePlugin here https://webpack.js.org/plugins/define-plugin/
you could use a relative path when you make any request to your api server instead of calling the full url for your app.
and for your development you could add a proxy property to your package.json file:
{
...
"proxy": {
"/api/*": {
"target" :"http://localhost:9876"
}
}
}
so whenever you make any request that prefixed with /api will be redirected to http://localhost:9876/api this is just in the development but in the production if you make a request prefixed with /api it won't be redirected it will be served normally because the proxy is just available in the dev server coming with create-react-app.

Multi Tenant App in React

I'm building a multi tenant app in React (with Webpack setup via base, dev and prod config files), and I'm wondering the best way to create and access per-tenant variables.
When I run my app with:
npm run start tenant1
I am able to access tenant1 in Webpack by using this:
const tenant1 = process.argv[process.argv.length -1];
However, now I'm wondering what is the best way to make that variable globally accessible. My hope is to use that variable to the create a folder structure within the app along the lines of:
/app/${tenant}/img/
/app/${tenant}/css/
/app/${tenant}/components/
Ideally without having to import a variable into every single javascript file.
Any suggestions or links to existing setups would be greatly appreciated.
Update Jan 2019:
I've found a way to achieve this with Create-react-app, not perfect but it works and achieves the following:
Run a React app on port 3000 that works for multiple domains simultaneously.
Forward all requests not handled by React to a back end.
Optionally use SSL in development.
Create-react-app has a proxy option that is very easy to setup. Simply add the following line to your package.json file:
"proxy": "http://localhost:5000"
However, this will not work for multiple domains. There is a more advanced proxy configuration available.
After following these steps, you will be able to control where different requests are sent, but it does not entirely provide the ability to proxy multiple domains - to achieve this:
Create the file .env in the root of your create-react-app project.
Add the following to it:
NODE_PATH=src/
DANGEROUSLY_DISABLE_HOST_CHECK=true
# optionally add this line for SSL in development
HTTPS=true
From the advanced proxy instructions above, you should end up with a file called setupProxy.js in the root of your /src folder - change to the following:
const proxy = require('http-proxy-middleware')
const options = { target: 'https://[::1]:8000', secure: false }
module.exports = function(app) {
app.use(proxy('/api', options))
app.use(proxy('/graphql', options))
}
The magic part is the https://[::1]: 8000, which will forward all domains from the root request to the same back end. This doesn't seem to be well documented anywhere, but I believe it is the IPv6 equivalent of 127.0.0.1.
After this, you can add entries to your hosts file (for example: 127.0.0.1 some-domain.example.com), and in your React app use just the paths (/api or /graphql) and requests should be proxied to the same domain as the browser is running on.
Original answer:
I ended up taking a fairly manual approach to this.
I'm using a react/redux boilerplate, but I've modified the npm scripts like so:
"start:tenant1": "cp -r ./tenants/tenant1 ./app/tenant && cross-env NODE_ENV=development node server",
"start:tenant2": "cp -r ./tenants/tenant2 ./app/tenant && cross-env NODE_ENV=development node server",
Each tenant folder is copied to the app when the development server is run with the relevant command, and files are named the same within each tenant folder (masthead.svg, vars.js, etc) so that imports throughout the app can be static.
Mostly this works because I'm not using a node server in production, a static build folder is generated by the boilerplate.
Hope this helps someone.

Resources