nodejs - How to correctly serve vue app from express - node.js

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));
});
}

Related

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

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"),
});
});

Api returning 404 on production

I have a next.js app and I'm trying to create an api. When I run it as development, the api's get called, but when I run it using next start I get a 404 error when calling the api's.
Here's the relevant server.js code:
app.prepare().then(() => {
require('./server/startup/routes')(server);
server.get('*', (req, res) => {
return handle(req, res);
});
const PORT = process.env.PORT || 5000;
server.listen(PORT, err => {
if (err) throw err;
console.log(`> Read on http://localhost:${PORT}`);
});
});
Here's the routes file
module.exports = app => {
app.use('/api/something-cool', cool);
};
Cool File:
const express = require('express');
const router = express.Router();
router.post('/', async (req, res) => {
...Code
res.send({ status: 'ok' });
});
module.exports = router;
The api route of /something-cool works when I run nodemon, but when I run next run, it returns a 404 error. What am I doing wrong and how can I fix it?
You are using a custom server (express) on top of Next.js to customize routes. This means that first, you have to build the Next.js App and then you have to run your server.js file in order to serve the App.
Option 1:
Builds the production application first
next build
Then run you server.js file:
NODE_ENV=production node server.js
more info here https://github.com/zeit/next.js/tree/master/examples/custom-server-express
Option 2:
There is also the option to create the API route within the Next.js App without using a custom server.
See https://github.com/zeit/next.js/tree/master/examples/api-routes for more info on how to do it.

Can I update index.html after start the Angular universal server?

I have requirement to convert a server-side rendering project to client-side render, but I have to generate/render header html from existing tech. My plan to render header part using node-express and attach to index.html in each server hit. Once server start, I am able to render header html and update the index.html with new html string. But it will work in first hit, from second hit updated index.html not being displayed in browser.
Existing tech stack:
Express, Handlebars
New tech stack:
Angular universal
Note: index.html is the build file using angular universal
server.ts
// -- code --
const fs = require('fs');
const parse = require('node-html-parser').parse;
var hbs = exphbs.create({
extname: '.hbs'
});
// All regular routes use the Universal engine
app.get('*', (req, res) => {
hbs.render('./dist/server/header.hbs',{header:dynamicData}).then(headerHTML => {
fs.readFile('./dist/browser/index.html', 'utf8' ,function(err, data) {
const root = parse(data);
const body = root.querySelector('header');
body.appendChild(headerHTML+Math.random()); // add a random number for testing
fs.writeFile("./dist/browser/index.html",root.toString(), function(err) {
res.render('index', { req });
});
});
});
});
// -- code --
cmd >> npm run build:ssr
cmd >> npm run serve:ssr

NodeJS + React + Next Framework adding a path to the route

I'm attempting to setup a NodeJS application that is using the Next framework to utilize client and server side rendering. I'm trying to get the client and server side rendering to prepend a path to the routes/URLs it generates. The server side render seems to be working by setting up the express server GET function to listen for requests made on route and then passing that along to node by stripping out the prepended route value. However when it comes the rendering on the client the prepended value is missing even when the as="{somestring}" is added to the .js pages for elements like Link so when the external Next javascript files are referenced in the render it's missing the prepended value.
The purpose for the routing is to allow us to run multiple micro-services on one domain each hosted on different instances in AWS and being routed using Target Groups and an ALB.
Essentially what I want to do is replace / with /{somestring} and I need this to be included not only in the server side rendering but in the client side rendering.
URL Example:
www.example.com -> www.example.com/somestring
HTML Render:
www.example.com/_next/960d7341-7e35-4ea7-baf6-c2e7d457f0db/page/_app.js -> www.example.com/somestring/_next/960d7341-7e35-4ea7-baf6-c2e7d457f0db/page/_app.js
Edit/Update
I've tried to use app.setAssetPrefix and while it renders the requests for the assets correctly and the pages load the assets themselves are 404ing.
Here is my server.js file:
const express = require('express');
const next = require('next');
const port = process.env.PORT || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
// Port
const server = express();
app.setAssetPrefix('test1');
// ======
// Routes
// ======
server.get('/test1/:id', (req, res) => {
const actualPage = `/${req.params.id}`;
const queryParams = { id: req.params.id };
app.render(req, res, actualPage, queryParams);
});
server.get('/test1', (req, res) => {
app.render(req, res, '/');
});
server.get('*', (req, res) => {
handle(req, res);
});
// =============
// End of Routes
// =============
server.listen(port, err => {
if (err) throw err;
console.log(`>Listening on PORT: ${port}`);
});
})
.catch(ex => {
console.error(ex.stack);
process.exit(1);
});
You need custom routing. Parse the incoming url and replace it with what you want.
Here is is an example to make /a resolve to /b, and /b to /a
https://github.com/zeit/next.js#custom-server-and-routing

Local Dev Server for AWS Lambdas

Is there a dev server that runs AWS Lambdas locally? My requirements would be
nodejs server, no ruby or go or anything need to install other than node and npm packages
Creates a server that I can query via wget / curl or an API testing tool to send various events to
I should be able to specify a js file that the server uses as lambda and the server should restart / update when I change that file
Here's a solution that does not require serverless or claudiajs.
I usually just write my own little express script for this purpose. I always just use Lambda Proxy integration so it's simpler.
Something like this...
const bodyParser = require('body-parser')
const express = require('express')
// Two different Lambda handlers
const { api } = require('../src/api')
const { login } = ('../src/login')
const app = express()
app.use(bodyParser.json())
// route and their handlers
app.post('/login', lambdaProxyWrapper(login))
app.all('/*', lambdaProxyWrapper(api))
app.listen(8200, () => console.info('Server running on port 8200...'))
function lambdaProxyWrapper(handler) {
return (req, res) => {
// Here we convert the request into a Lambda event
const event = {
httpMethod: req.method,
queryStringParameters: req.query,
pathParameters: {
proxy: req.params[0],
},
body: JSON.stringify(req.body),
}
return handler(event, null, (err, response) => {
res.status(response.statusCode)
res.set(response.headers)
return res.json(JSON.parse(response.body))
})
}
}
Then, run it with nodemon so it watches the files and reloads as necessary.
nodemon --watch '{src,scripts}/**/*.js' scripts/server.js
Have you checked out SAM Local? https://github.com/awslabs/aws-sam-local

Resources