How to save rendered html view files from ExpressJS Routes - html

I've built few pages of a static website using ExpressJS and PUG to get the advantage of the template engine.
But now I need to export all the raw HTML that is being rendered by all ExpressJS Routes.
Is there any package that can help me to do that? Or I've to write custom command and iterate over all the Routes and save the rendered output?
If a custom command is the only way, how do I iterate over all the routes and get the rendered output?

I couldn't find any library or resource to achieve what I wanted. But with some of my dirty code, hacks, and packages I was able to export all the routes.
Note: Instead of writing a node command to export the htmls, I've added a route to trigger the operations here is the code for the route:
app.use('/export_templates', router.get('/', async function (req, res, next) {
const endpoints = listEndpoints(app);
const failedEndpoints = [];
for (const i in endpoints) {
const endpoint = endpoints[i];
if (endpoint.path == '/export_templates') {
continue;
}
try {
const res = await axios.get('http://'+req.headers.host+''+endpoint.path+'?export=true');
}
catch(error) {
failedEndpoints.push(endpoint.path);
}
}
res.json({
"status": "succes",
"message": "Please check templates folder for the latest exported html templates",
"failed": failedEndpoints
})
}));
Basically this route iterates and makes a request to all the available routes with a export=true parameter.
Then inside every route view function a condition checks if the export parameter is available then calls the exportTemplateFile function with the pug template location and new file name as the function parameter.
If the request doesn't contain export parameter the requested route will simply output what template.
An example route:
router.get('/', function(req, res, next) {
if (req.query.export) {
exportTemplateFile('views/index.pug', 'index.html');
}
res.render('index.pug');
});
And here is the code for 2 util function to complete the export process
function createTemplateFile(filename) {
fs.open(filename,'r',function(err, fd){
if (err) {
fs.writeFile(filename, '', function(err) {
if(err) {
console.log(err);
}
});
}
});
}
function exportTemplateFile(templateLocation, templateName) {
const html = pretty(pug.renderFile(templateLocation));
createTemplateFile('templates/'+templateName);
var stream = fs.createWriteStream('templates/'+templateName);
stream.once('open', function (fd) {
stream.write(html);
stream.end();
});
}
The createTemplateFile function simply creates a new file if it doesn't exist.
The exportTemplateFile function saves the HTML in the html variable rendered by pug and prettifies it with the pretty package and then overwrites the new template file.
Note: In my case all the pug templates were static so I didn't have to pass any context to the pug.renderFile function. But if you need any context to be used inside the pug template you can simply pass that with the template location.

Edited version of the same answer.
First of all thank you so much for solving this problem.
I have made some changes to your code as per new errors.
Here is the code with async and await function for ejs users
const express = require('express')
const ejs = require('ejs')
const fs = require('fs')
const app = express()
const port = 3000
//set the templating engine as ejs
app.set('view engine', 'ejs');
function createTemplateFile(filename) {
fs.open(filename,'r',function(err, fd){
if (err) {
fs.writeFile(filename, '', function(err) {
if(err) {
console.log(err);
}
});
}
});
}
async function exportTemplateFile(templateLocation, templateName) {
var html = await ejs.renderFile(templateLocation);
createTemplateFile('templates/'+templateName);
var stream = fs.createWriteStream('templates/'+templateName);
stream.once('open', function (fd) {
stream.write(`${html}`);
stream.end();
});
}
app.get('/', (req, res, next) => {
res.render('./pages/home')
exportTemplateFile('views/pages/home.ejs', 'index.html');
console.log('file rendered and saved successfully')
})
app.listen(port, () => {
console.log(`App is listening on port ${port}`)
})

Related

ReferenceError when trying to use a variable in an EJS template file

I'm having a heap of trouble just trying to get an EJS template file to recognise a variable that stores the rows of an SQLite3 table query in a corresponding .js file. I get a ReferenceError for the variable I used in the EJS file when launching the server and trying to access that route.
For context it's a micro blog project where I'd like authors to have the ability to save draft articles in to a database and for the user to be able to come back and modify or publish them later.
Here's my 'author.js' file:
// Author Page
const express = require("express");
const router = express.Router();
const assert = require('assert');
/**
* #desc retrieves draft articles
*/
router.get("/author-home", (req, res, next) => {
//Use this pattern to retrieve data
//NB. it's better NOT to use arrow functions for callbacks with this library
global.db.all("SELECT * FROM draftArticles", function (err, rows) {
if (err) {
next(err); //send the error on to the error handler
} else {
res.json(rows);
}
});
});
/**
* #desc Renders the author page
*/
router.get("/author", (req, res) => {
res.render("author-home", data);
});
module.exports = router;
In my 'author-home.ejs' file, I'm trying to insert various article properties in a element like so:
<td><% data[0].article_title %> </td>
<td><% data[0].article_subtitle %> </td>
...etc.
Can anyone tell me what I'm doing wrong? I can also post the code for my 'index.js' file if that's helpful. Many thanks in advance
EDIT:
After some suggestions were sent and the scope issue of the 'data' variable was highlighted, I corrected my code in author.js (at least, I believe so) to the following:
// Author Page
const express = require("express");
const router = express.Router();
const assert = require('assert');
router.get('/author-home', (req, res, next) => {
global.db.all('SELECT * FROM draftArticles', function (err, rows) {
if (err) {
console.log("No data found.")
next(err); //send the error on to the error handler
return;
}
res.render('author-home', { data: rows });
});
});
module.exports = router;
However, I still receive a referenceError when trying to access data in my EJS file.
I also tried, as was suggested, to pass static data like so:
let dummyData = "This is test data";
router.get('/author-home', (req, res, next) => {
res.render('author-home', { data: dummyData });
});
Also receiving a referenceError.
This is because you have not defined "data". You need to define it if you want to send an array you can use
How can I pass an array to an ejs template in express?
your code should be like..
// Author Page
const express = require("express");
const router = express.Router();
const assert = require('assert');
router.get('/author-home', (req, res, next) => {
global.db.all('SELECT * FROM draftArticles', function(err, rows) {
if (err || !rows || !rows.length) {
console.log("No data found.")
// also try to log rows here to see what you are getting. does the "rows" have atricle_title etc attributes or not?
next(err || new Error("No Data found!")); //send the error on to the error handler
return;
}
res.render('author-home', {
data: rows
});
});
});
module.exports = router;

Error handler ignored when NODE_ENV=production

I am building a simple REST API with Node/Express, and I'm having a hard time when I deploy it to production. When NODE_ENV=development, everything works as expected. I get back the JSON error and the correct status code. When NODE_ENV=production, I only get back an HTML page with the default error message and nothing else. I can read the status code, but I need to have access to the full JSON payload to identify the errors better. This is my code:
import Promise from 'bluebird'; // eslint-disable-line no-unused-vars
import express from 'express';
import config from './config';
import routes from './routes';
import { errorMiddleware, notFoundMiddleware } from './middlewares/error.middleware';
import mongoose from './config/mongoose.config';
// create app
const app = express();
(async () => {
// connect to mongoose
await mongoose.connect();
// pretty print on dev
if (process.env.NODE_ENV !== 'production') {
app.set('json spaces', 2);
}
// apply express middlewares
app.use(express.json());
// register v1 routes
app.use('/v1', routes);
// catch errors
app.use(notFoundMiddleware);
app.use(errorMiddleware);
// start server
app.listen(config.port, () => console.info(`server started on port ${config.port}`));
})();
export default app;
This is the notFoundMiddleware:
export default (req, res, next) => next(new Error('Not Found'));
This is the errorMiddleware:
const errorMiddleware = (err, req, res, next) => {
console.log('test'); // this works in development, but not in production
const error = {
status: err.status,
message: err.message
};
if (err.errors) {
error.errors = err.errors;
}
if (process.env.NODE_ENV !== 'production' && err.stack) {
error.stack = err.stack;
}
return res.status(error.status || 500).send({ error });
};
If you are runing on production server, try to use some logging provider like "papertrailapp" to see the error occurs in your app.
I've just stumbled upon the same problem. It turned out it's caused by a transpiler optimization applied when building production bundle - this one: https://babeljs.io/docs/en/babel-plugin-minify-dead-code-elimination
Express' error handlers should have the signature (err, req, res, next) => { ... } (be of arity 4). In your example next is not used anywhere in errorMiddleware function body and thus it gets eliminated (optimized-out) from function signature in production code.
Solution:
use keepFnArgs: true plugin option - possibly through https://webpack.js.org/plugins/babel-minify-webpack-plugin/ webpack configuration:
var MinifyPlugin = require("babel-minify-webpack-plugin")
module.exports = {
// ...
optimization: {
minimizer: [
new MinifyPlugin({
deadcode: {
keepFnArgs: true,
},
}, {}),
],
}
// ...
}
or alternatively pretend in your code that this argument is used:
const errMiddleware = (err, req, res, _next) => {
// ... your code ...
// ...
// cheat here:
_next
}

Require a file within a route to run a query

Im trying to set up my routes and then include a "content" file that would run a query
app.get('/participants', function(req, res, next) {
var participants = require('./content/participants');
});
and then the participants file:
const db = database.connect('olmsdb.1sserver.com', 'campyio');
db.raw('SELECT * FROM participants').then(function(results) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(results));
return results;
db.destroy();
});
The goal here is to hit the /participants route and then run the the select query and send the query results.
require() doesn't work like source(..) or execute in other languages. The code in it only execute once when first required. Then the module is cached.
You need to return functions and classes in a module file, using module.exports etc.
To do this:
participants.js:
const db = database.connect('olmsdb.1sserver.com', 'campyio');
exports.getParticipants = function() {
return db.raw('SELECT * FROM participants');
}
app.js:
var participants = require('./content/participants');
app.get('/participants', function(req, res, next) {
participants.getParticipants().then(function(results) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(results));
next();
});
});
You need to put that code in an exported function so that you can call it with the request & response every time you get a request.
That means there is no point in require()ing it inside your route handler.

show json data in index file

I dont understand why I cant display my json data. I am new to javascript and I want to display the data in the json file to my index file.
I have used the express generator for all the files. I did read that I should add this FS code in my app.js, but I cant use the data variable in my index file in my view. Any help ?
var express = require('express');
var router = express.Router();
var fs = require('fs');
/* GET home page. */
router.get('/', function(req, res, next) {
var file = __dirname + '/public/list/list.json';
var data;
fs.readFile(file, 'utf8', function (err, data) {
if (err) {
console.log('Error: ' + err);
return;
}
data = JSON.parse(data);
console.log(data);
});
res.render('index', { title: data });
console.log(data);
});
module.exports = router;
here is my json file
{
"username":"xyz",
"password":"xyz#123",
"email":"xyz#xyz.com",
"uid": 1100
}
fs.readFile is asynchronous , so you should put res.render(..) inside his callback , because it will fired when the readFile function ends. So change your code to :
fs.readFile(file, 'utf8', function (err, data) {
if (err) {
console.log('Error: ' + err);
return;
}
data = JSON.parse(data);
console.log(data);
res.render('index', { title: data });
});
The above answer is correct, but there's also an alternative.
If you're using this file for your index page, it'd be used a lot. If the data isn't changing, you can simply require the JSON file at the top of your code and return it in the request.
var express = require('express');
var router = express.Router();
var list = require(__dirname + '/public/list/list.json');
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: list });
});
module.exports = router;
However, if that data does change frequently, reading the file is the way to go.

Angular Seed project, set index.html by default

I am using the Angular Seed project to build a simple website. When i start the node server and enter the url at localhost:8000, it serves up the directory contents. I would like it to serve up the index.html file but would like to do this without a redirect.
I believe that I need to modify the following function and that I should change the code for the isDirectory check but I'm not sure if that is the correct way to go about doing this. Any suggestions would be appreciated.
StaticServlet.prototype.handleRequest = function(req, res) {
var self = this;
var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
return String.fromCharCode(parseInt(hex, 16));
});
var parts = path.split('/');
if (parts[parts.length-1].charAt(0) === '.')
return self.sendForbidden_(req, res, path);
fs.stat(path, function(err, stat) {
if (err)
return self.sendMissing_(req, res, path);
if (stat.isDirectory())
return self.sendDirectory_(req, res, path);
return self.sendFile_(req, res, path);
});
}
Update #1
I have two screenshots to clarify. The first image is what I currently get, the second image is what I want.
What I Get
What I Want
Update #2
Using the link to Restify below I found the following example which is exactly what I needed.
var server = restify.createServer();
var io = socketio.listen(server);
server.get('/', function indexHTML(req, res, next) {
fs.readFile(__dirname + '/index.html', function (err, data) {
if (err) {
next(err);
return;
}
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.end(data);
next();
});
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
server.listen(8080, function () {
console.log('socket.io server listening at %s', server.url);
});
The angular-seed project is a starting point for the client, but not really for the server side.
They included a simple web-server node script without dependencies. This way you don't need npm or other modules.
For the server side you can use node with connect/express or any other web server/language.
You just need to make rest services and serve some static html.
Since you have already installed Node restify may be something for you.
Update: I created a basic sample for using the angular-seed with restify:
https://github.com/roelandmoors/restify-angular-seed