.map is not a function when getting React files from build folder - node.js

I have a node/express application that's using React for the frontend. Everything works fine in development mode where there's a proxy between the backend running on localhost:5000 and the frontend on localhost:3000. However, I'm running into an issue for my production build where I'm getting the static React files from the build folder by including the following in my main server file:
app.use(express.static(path.join(__dirname, "client", "build")));
app.get("*", function (req, res) {
res.sendFile("index.html", {
root: path.join(__dirname, "client", "build"),
});
});
With this setup, I'm able to see the React app on localhost:5000, but on certain routes/pages I receive TypeError: a.map is not a function. The errors all seem to be triggered by arrays that are created in a context provider that sets state from APIs using axios. Here's an example of one of the API calls:
const getNodesApi = {
url: "model/get-all-nodes",
method: "GET",
headers: {
token: localStorage.token,
},
};
async function getNodeData() {
await axiosPm(getNodesApi)
.then((response) => {
const resData = response.data;
setNodeData(resData);
setLoading(false);
})
.catch((error) => {
console.log(error.response);
});
}
Is there something I have to do to set the base URL for the APIs or could something else be causing the TypeError: a.map is not a function?
Edit 1: Example where .map is failing
const [options, setOptions] = useState(
processes.map((process, index) => ({
key: index,
value: process.process_id,
label: process.process_name,
}))
);
In this example processes is coming from the context provider. It's initially defined by:
const [processes, setProcesses] = useState([]);

Answering my own question for visibility. The issue was being caused because I was mounting my backend/API routes after doing app.get for the React index.html. To resolve, mount the backend routes first.
app.use(express.static(path.join(__dirname, "client", "build")));
//Routes - mounted from backend for API
mountRoutes(app);
//Routes - use from React app for frontend
app.get("*", function (req, res) {
res.sendFile("index.html", {
root: path.join(__dirname, "client", "build"),
});
});

Related

Is there a mechanism to force express redirect to use host paths?

I have the following code snippet made with express js
import serverless from 'serverless-http'
import express from 'express';
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.get('/api/info', (req, res) => {
res.send({ application: 'sample-app', version: '1.0' });
});
app.get('/jump', (req, res) => {
res.redirect('/api/info');
});
app.get('/explicit-jump', (req, res) => {
res.redirect('/dev/api/info');
});
app.post('/api/v1/getback', (req, res) => {
res.send({ ...req.body });
});
export default serverless(app)
If I deploy that code with serverless probably I will get an endpoint like https://my-api.some-region.amazonaws.com/dev/
Now if I try to reach the endpoint that redirect without the '/dev' path (/jump), I will get forbidden because is trying to reach https://my-api.some-region.amazonaws.com/api/info.
The one that set the path explicitly (/explicit-jump) works fine.
Fixing this single case is easy but I'm in the context of using an external app boilerplate (shopify express app) that has an incredible amount of redirects, really a high number.
I tried using a middleware that rewrites the urls when redirects:
app.use((req, res, next) => {
const redirector = res.redirect
res.redirect = function (url) {
console.log('CHANGING: ' + url);
url = url.replace('/api', '/dev/api')
console.log('TO: ' + url);
redirector.call(this, url)
}
next()
})
But I have the feeling that this is a brute force idea and actually it worked only in some occasions, somehow there are still redirects that goes to the base url ignoring the '/dev' path.
Is there a way I could fix this in a reasonable way so all redirects use the host in where the function is running?

problem while POSTing to the server in Express

I'm learning Express and I face an issue which I can't understand.
When I route to /addPerson I expect to log the name: 'Mike', age: 30 to the console. Instead I got nothing logged to the console. What's wrong in my code?
here's the server.js code
const Express = require('express'),
app = Express(),
PORT = process.env.PORT || 5000,
parser = require('body-parser'),
data = []
// initialize the main project folder
app.use(Express.static('public'))
// running the server
app.listen(PORT, () => {
console.log(`Server is running at port ${PORT}`);
})
// include body parser to handle POST requests
app.use(parser.urlencoded({extended: false}))
app.use(parser.json())
// setup CORS
const cors = require('cors')
app.use(cors())
// GET request
app.get('/', (req, res) => {
res.send('<h1>Home Page</h1>')
})
app.get('/addPerson', (req, res) => {
res.send('<h1>Hello Hany</h1>')
})
// POST request
app.post('/addPerson', (req, res) => {
data.push(req.body)
console.log(data);
})
and here is the client side app.js code
const postData = async ( url = '', data = {})=>{
console.log(data);
const response = await fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
// Body data type must match "Content-Type" header
body: JSON.stringify(data),
});
try {
const newData = await response.json();
console.log(newData);
return newData;
}catch(error) {
console.log("error", error);
}
}
postData('/addPerson', {name: 'Mike', age: 30});
this the files structure
Alright, I've taken a look at your code and this is what I've noticed. Within your server.js file you have this code block:
app.get('/addPerson', (req, res) => {
res.send('<h1>Hello Hany</h1>')
})
That is sending back a static H1 tag when the user creates a get request to localhost:5000/addPerson. Then, directly below that you have your post route but you're never fully accessing that from anywhere (I looked through all your app.js code to double check).
Instead, I have changed your code to return a static html file with a button that allows you to call this function (just as an example so you can see that your routes do in fact work). This isn't the cleanest solution to your problem but I just wanted to make sure you see where the problem lies as I've been in your shoes before when I first started working with express. You can take a look at the CodeSandbox I setup below to replicate your issue and take a look through all the code to get an understanding.
To properly solve your issue using the app.js file you would have to serve the javscript file as your "frontend". Personally I'm a big fan of React so I usually serve my frontend with React, while my backend is express. You can also very easily serve this file using NodeJS in a similar fashion that you are with your "backend". If you were to take the React approach you would be able to modify this code:
app.get("/addPerson", (req, res) => {
res.sendFile(path.resolve(__dirname, "public", "index.html"));
});
To find the frontend section you desire using React (I can recommend react-router if you require multiple routes but I don't want to overwhelm you with too much information yet) and complete the same function. If you have any questions feel free to reach out and let me know! Hopefully this helps!

nodejs - How to correctly serve vue app from express

I have a cli script that I'm converting to a local webapp using express to run a local server and pkg to create an executable that will include all the needed files. I'm using vue for the front-end and during the app development I didn't have any issue. I've tried to do a test build and when I try to launch the app I will get this error message in the opened browser tab Error: cannot GET /.
In my express routes I didn't created the default path for the root / so I suppose that the problem is with this aspect. I've correctly added the express.static() middleware but I'm not sure if I need to send the index.html file that run the vue app using express. How I configure correctly the root endpoint to serve the vue app?
Another small thing about the problem, how I can launch google chrome or default browser in linux using the exec() function of node child_process module?
Can anyone help me please?
async openBrowser(){
this.app.use(express.static(this.assets));
this.app.use(express.json());
this.app.use(express.urlencoded({extended: true}));
http.createServer(this.app).listen(8990, () => {
console.log(chalk.yellowBright('Starting quizist...'));
switch(process.platform){
case 'darwin':
this.child = exec('open --new -a "Google Chrome" --args "http://localhost:8990"');
break;
case 'win32':
this.child = exec('start chrome http://localhost:8990');
break;
}
});
// I have added the root endpoint but it's not clear for me how to serve the vue app
this.app.get('/', (req, res) => {
});
this.app.get('/init', (req, res) => {
this.extractQuestions();
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(this.askQuestions));
});
this.app.post('/checkanswer', async (req, res) => {
this.userAnswer = req.body;
const data = await this.checkAnswers();
res.setHeader('Content-Type','applications/json');
res.send(JSON.stringify(data));
});
this.app.get('/results', (req, res) => {
const data = {
userScore: this.score,
questionsNumber: this.askQuestions.length,
answeredQuestions: this.answeredQuestions,
correctAnswers: this.correctAnswers
}
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(data));
});
}

How to serve index.html for React and handle routing at the same paths?

Using react you need to serve index.html with react_app.js included in it at any route if user have not downloaded react_app.js (came first time).
Then you need to serve some api calls from react_app.js, but if you use same url for GET, let's say, you will get API call response and not the index.html with react_app.js.
What is the solution for this? Make api calls only with some prefix and send index.html only if route not found?
My code:
fastify.register(require('fastify-static'), {
root: path.join(__dirname, './static')
})
fastify.route({
method: 'GET',
url: '/',
handler: async (req, res) => {
res.send('you will get api call answer but you need to serve index.html with react_app.js first!!!!')
}
})
As #Garrert suggested, this is how it will work:
// Статические файлы
fastify.register(require('fastify-static'), {
root: path.join(__dirname, './static')
})
// this will work with fastify-static and send ./static/index.html
fastify.setNotFoundHandler((req, res) => {
res.sendFile('index.html')
})
// Here go yours routes with prefix
fastify.register(async openRoutes => {
openRoutes.register(require('./server/api/open'))
}, { prefix: '/api' })

Serving VueJS Builds via Express.js using history mode

I want to serve vue js dist/ via express js. I am using history router in vue js app.
The following are the api calls
api/
s-file/sending/:id
terms/get/:which
As i have figured out a solution in python here. I don't know how to do it in node js with express
The code i am using right now is
app.use(function (req, res, next) {
if (/api/.test(req.url))
next();
else {
var file = "";
if (req.url.endsWith(".js")) {
file = path.resolve(path.join(distPath, req.url))
res.header("Content-Type", "application/javascript; charset=utf-8");
res.status(200);
res.send(fs.readFileSync(file).toString());
} else if (req.url.endsWith(".css")) {
file = path.resolve(path.join(distPath, req.url))
res.header("Content-Type", "text/css; charset=utf-8");
res.status(200);
res.send(fs.readFileSync(file).toString());
} else {
file = path.resolve(path.join(distPath, "index.html"))
res.header("Content-Type", "text/html; charset=utf-8");
res.status(200);
res.send(fs.readFileSync(file).toString());
}
}
})
Have a look at connect-history-api-fallback that is referenced in the vue docs.
This should solve your problems.
Example using connect-history-api-fallback
var express = require('express');
var history = require('connect-history-api-fallback');
var app = express();
// Middleware for serving '/dist' directory
const staticFileMiddleware = express.static('dist');
// 1st call for unredirected requests
app.use(staticFileMiddleware);
// Support history api
// this is the HTTP request path not the path on disk
app.use(history({
index: '/index.html'
}));
// 2nd call for redirected requests
app.use(staticFileMiddleware);
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
The very simpler one if anyone wants to use
Just add this below all the valid routes and above app.listen
app.all("*", (_req, res) => {
try {
res.sendFile('/absolute/path/to/index.html');
} catch (error) {
res.json({ success: false, message: "Something went wrong" });
}
});
Make sure you have included
app.use(express.static('/path/to/dist/directory'));

Resources