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
Related
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));
});
}
Problem
I just started out with Node.js my plan was to first set up a Server with some basic HTML and static files(css,js).
But when i try to Serve the static files with express.js or even without express the js/css code is getting replaced from my index.html code. Without Node.js everything seems to work fine i even tried it with flask in python which worked fine too.
Are there any common reasons for this?
Node.js code
var http = require("http");
var fs = require("fs");
var express = require("express");
var app = express();
app.use("/", express.static("public"));
http
.createServer(function(req, res) {
fs.readFile("index.html", function(err, data) {
if (err) {
res.writeHead(404, { "Content-Type": "text/html" });
return res.end("404 Not Found");
}
res.writeHead(200, { "Content-Type": "text/html" });
res.write(data);
return res.end();
});
})
.listen(8080);
Pictures
So even though you are using express to serve static files, you are not using express as server instead of that you are making a manual server which serves index.html for EVERY request.
http
.createServer(function(req, res) {
fs.readFile("index.html"....);
})
.listen(8080);
What this code means is create a server, and for each request read the index.html file and serve this
So when the request is http://localhost:8080/css.css it doesn't discriminate.
I would recommend reading about creating servers in node a little more. But the solution is use express as server.
var http = require("http");
var fs = require("fs");
var express = require("express");
var app = express();
app.use("/", express.static("public"));
app.listen(8080, ()=>{
console.log('Server started');
})
This will work just fine GIVEN that index.html IS IN A FOLDER NAMED PUBLIC
From the doc,
For example, use the following code to serve images, CSS files, and
JavaScript files in a directory named public:
app.use(express.static('public'))
Now, you can load the files that are
in the public directory:
Note, if your files are in your project root you can use:
app.use("/", express.static("."));
I'm trying to make a root where there's all my front end scripts.
So when I want to import my scripts in my index.html I just need to do :
<script src="/front/scripts"></script>
First problem :
This is my server code :
app.use('/front/scripts', (req, res) => {
const readScript = (filePath) => {
fs.readFile(filePath, 'utf-8', (err, data) => {
if (err) throw err;
return res.write(data);
});
};
readScript('./node_modules/jquery/dist/jquery.js');
readScript('./node_modules/socket.io-client/dist/socket.io.js');
readScript('./src/js/app.js');
});
It's not working because express is keep loading the page so I can't import it because my index.html is waiting for my front/scripts to stop loading.
Second problem:
It will not work because of the size of the file, the lightest file will be loaded faster than a bigger one like jQuery, so the lightest file will be at the top of front/scripts and the big one will be after le lightest.
For example if my personal app.js is just a 3 lines jQuery script that change the content of a div, it will return me an error because my script is loaded before jQuery.
How can I do this properly?
Once all scripts have been read and written to response finally the response must get finalized as well using res.end();
Example below has a small change as the readFile is async so there's no guarantee that res.end() is called at the right time. Made it synced.
See here fs.readFileSync.
app.use('/front/scripts', (req, res) => {
const readScript = (filePath) => {
res.write(fs.readFileSync(filePath, 'utf-8'));
};
readScript('./node_modules/jquery/dist/jquery.js');
readScript('./node_modules/socket.io-client/dist/socket.io.js');
readScript('./src/js/app.js');
res.end();
});
Use express.static
Example:
Sample project setup:
server.js
front
scripts
app.js
server.js:
const express = require('express')
const { join } = require('path')
const app = express()
app.use(express.static(join(__dirname, 'front/scripts'))
app.listen(3000)
You'll get your app.js here: localhost:3000/app.js
I would like to serve up a ReactJS Single Page App from a nodeJS server and pass up some JSON data at the same time.
I have user credentials and some other user specific data that i would like pre-populated into my page and avoid making multiple round trips to the server.
How do i pass a JSON object to the client at request time and have it available to my React app
var path = require('path');
const express = require('express');
const app = express();
const port = process.env.PORT;
app.use(express.static('dist'));
app.get('/*', function(req, res) {
res.sendFile(path.join(__dirname, '../../dist/index.html'));
});
app.listen(port, () => console.log(`Running on port ${port}.`));
I can suggest you add a script tag into your index.html file. Like below
<script>
window._DATA_={ key: 'value', .....}
</script>
Now in your react application, use the key window._DATA_ to get the data that you sent from the server. In this approach, the problem is that you can't send dynamic data.
To achieve that you may need to use the template libraries. For example pug, nunjucks, ejs, etc.
Below is the example of using pug.
Your express route will look like this.
app.get('/*', function(req, res) {
res.render('index', data);
});
// End of your pug file looks like
...
...
script.
var window._DATA_= !{JSON.stringify(data)}
If you want to add scripts files dynamically then you can use html-webpack-pug-plugin.
For more info
https://www.npmjs.com/package/html-webpack-pug-plugin
I am building my first application using NodeJS & ExpressJS for the backend and AngularJS front end. I have all my front end routes working how I want, but I cannot figure out how to properly configure the Node backend so that when a specific URL is entered into the address bar, Node renders only the same Angular app every time.
For example, if I use About as a link to my about page in Angular, then the correct view will render and the address bar will show localhost:8080/about. However, if I just manually type in localhost:8080/about then Node responds with Cannot GET /about. Now I understand this is because my backend currently only handles the following:
var express = require('express');
var crypto = require('crypto');
var app = express();
app.set('views', __dirname + '/public');
app.engine('html', require('ejs').renderFile);
app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res){
res.render('index.html');
});
// API
app.get('/api/sync', function(req, res){
// Here we generate a 32 byte string and make it the key
var num = Math.floor(Math.random()*100);
var key = crypto.createHash('md5').update(num.toString()).digest('hex');
key = key.slice(0, key.length/2);
console.log('Created key: ' + key);
res.send(key);
});
var server = app.listen(8080, function(){
console.log('Listening on port %d', server.address().port);
});
So what I want to do is make it so EVERY request to Node renders the same index.html page but then properly routes the view in Angular based on the URL. What is the best way to do this?
I just realized that using:
app.get('*', function(req, res){
res.render('index.html');
});
And placing this after all other routes I want to catch first will work.
Since I don't have enough reputation yet to just add a comment, it's worth noting that res.render() won't work if you're not using a server-side template rendering engine (as you are using EJS). You would instead want to use something like res.sendFile() if you were just serving a static HTML and Angular page with all the routing set up in Angular.
app.get( '*', function( req, res ) {
res.sendFile( __dirname + '/public/index.html' );
} );
The best way handle angular route in angular-app and backend route in backend.
Angular/Frontend:
sampleApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'templates/home.html',
controller: 'MainController'
}).
when('/about', {
templateUrl: 'templates/about.html',
controller: 'AboutController'
}).
// >>> redirect other routes to
otherwise({
redirectTo: '/'
});
}]);
Backend:
For render static html you don't need app.get(...)
simple place index.html into:
public/index.html
and express serve it as html. Other not exists pages(routes) return 404 error and it is right.
In this case API fully separate and independent and angular fully single page app. Express serve static resources needed for angular.