Express.js (4.13.1) and Handlebars view engine - how to setup? - node.js

My project directory is called travel which has required dependencies node_modules and a subdir called views. Views subdirectory has layouts subdirectory. In the ~/travel/views/layouts there is .handlebar templates for the views. But when I go to the browser and type in localhost:3000/about or just localhost:3000 regardless of that I get 404 - not found I tried switching the res.render("about"); to res.render("/views/layouts/about"); but that does not work neither. It has something to do with the paths but I'm not sure what.
var express=require("express");
var app=express();
// set up handlebars view engine
var handlebars=require("express3-handlebars")
.create({defaultLayout:"main"});
app.engine("handlebars",handlebars.engine);
app.set("view engine","handlebars");
app.set("port",process.env.PORT || 3000);
app.get("/",function(req,res){
res.render("home");
});
app.get("/about",function(req,res){
res.render("about");
});
// custom 404 page (middleware)
app.use(function(req,res,next){
res.status(404);
res.render("404");
});
// custom 500 page (middleware)
app.use(function(err,req,res,next){
console.error(err.stack);
res.status(500);
res.render("500");
});
app.listen(app.get("port"),function(){
console.log("Express started on http://localhost:"+app.get("port")+";press ctrl+c to terminate,");
});

Assuming you have a subdirectory called views within the same directory as your Express app script, you need to set the Express views app setting like:
app.set('views', __dirname + '/views');
By default, the path is relative to the current working directory.
Additionally, you may wish to update your express3-handlebars module to be express-handlebars instead, as the latter is the newer of the two and is recommended by the current maintainer.

Try this by overriding the default layout
app.get('/', function (req, res, next) {
res.render('home', {layout: false}); });

Related

How redirect from a hbs(handlebars) page to a html page

I am working on my portfolio project. I am using handlebars and nodeJS.
I have a section called projects where I list 4 different projects to showcase. I have an index.js where all my routing is done. Projects 1,3,4 work just fine. These all will be clicked on from a project section on my index page.
However, I am so lost on how to route my project 2. Projects 1,3,4 are all handlebars(.hbs). But my project 2 is an html page. As seen in my code snippet, the html is store under the same /public folder as all my other code. However, I still get the error "Failed to lookup view "../public/views/fountainWebsite/html/home" in views directory". I am not sure how this needs to be done??
// === VARIABLES === //
var express = require('express');
var app = express();
var handlebars = require("express-handlebars");
var path = require("path");
var router = express.Router(); //creates a router object
//===== view ENGINE SET UP =====//
app.set('view engine', 'handlebars');
app.engine(
"hbs",
handlebars({
layoutsDir: path.join(__dirname, "/public/views/layouts"),
partialsDir: path.join(__dirname, "/public/views/partials"),
extname: ".hbs", //expected file extension for handlebars files
defaultLayout: "layout" //default layout for app, general template for all pages in app
})
);
app.set("views", path.join(__dirname, "views"));
//thought this would maybe fix the error?? It didn't//
// app.set("fountainWebsite", path.join(__dirname, "fountainWebsite")); //
app.set("view engine", "hbs");
app.use("/public", express.static(path.join(__dirname, "public")));
//===== .GET PAGES =====//
app.get('/', (req, res, next) => {
res.render('../public/views/index', {title: 'Home Page', css:['../public/css/style.css'], js:['../public/js/navBar.js']});
});
app.use('/', router);
router.get('/project1', (req, res, next) => {
res.render('../public/views/partials/project1', {title: 'Data Structures', css:['../public/css/projects.css'], js:['../public/js/navBar.js']});
});
//error here//
router.get('/project2', (req, res, next) => {
res.render('../public/views/fountainWebsite/html/home');
});
router.get('/project3', (req, res, next) => {
res.render('../public/views/partials/project3', {title: 'This Portfolio', css:['../public/css/projects.css'], js:['../public/js/navBar.js']});
});
router.get('/project4', (req, res, next) => {
res.render('../public/views/partials/project4', {title: 'Dictionary', css:['../public/css/projects.css'], js:['../public/js/navBar.js']});
});
It is important that we understand what our code is doing. Let's start with the route handler for /project2:
router.get('/project2', (req, res, next) => {
res.render('../public/views/fountainWebsite/html/home');
});
This is telling Express to listen for a request with the path /project2 and to render the file at ../public/views/fountainWebsite/html/home.
Handlebars requires an extension (like .hbs) in order to know which view engines to use to render the file. As we have omitted an extension from our file path, Express will assume the extension is .hbs because that's what we told it to do when we called app.set("view engine", "hbs");
I have to assume that your file path with extension is ../public/views/fountainWebsite/html/home.html since you have put it in a folder called "html". If this is the case, Handlebars is not going to find this file because it is looking for ../public/views/fountainWebsite/html/home.hbs and it will throw an Error.
The simple solution would be to rename this file with a .hbs extension. This will at least allow Express to locate the file and render it through the Handlebars view engine.
However, one problem with this approach may be that this view gets rendered wrapped in your layout.hbs file and you do not want that. Perhaps you want this HTML file to be unprocessed and served as a static asset.
In this case, you could remove the /project2 route handler and let Express serve this file as a static asset. Since you have already registered your public/ folder as static assets folder with the call app.use("/public", express.static(path.join(__dirname, "public"))); and your file path shows that this HTML file is already within the public/ folder, you should be able to request this HTML directly from your browser at the path /public/views/fountainWebsite/html/home.html.

Use `index.html` rather than `index.ejs`

I have a mean-stack application. By going to https://localhost:3000/#/home, it reads views/index.ejs. Here is the setting in app.js:
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.static(path.join(__dirname, 'public')));
app.all('/*', function(req, res, next) {
res.sendFile('index.ejs', { root: __dirname });
});
Actually, I don't use the feature of ejs in index.ejs. So now I want to use just a index.html rather than index.ejs.
I put the content of index.ejs in public/htmls/index.html and views/index.html. And here is the current setting in app.js:
var app = express();
// app.set('views', path.join(__dirname, 'views'));
// app.set('view engine', 'ejs');
app.use(express.static(path.join(__dirname, 'public')));
app.all('/*', function(req, res, next) {
res.sendFile('index.html', { root: __dirname });
// res.sendFile('index.html'); // does not work either
});
However, running https://localhost:3000/#/home returns
Error: No default engine was specified and no extension was provided.
Does anyone know how to fix it?
Edit 1: by following the answer of user818510, I tried res.sendFile('index.html', { root: path.join(__dirname, 'views') }); in app.js, it still can NOT find index.html.
Whereas, in routes/index.js, the following can find index.html, but it gives a warning express deprecated res.sendfile: Use res.sendFile instead routes/index.js:460:9.
var express = require('express');
var router = express.Router();
var path = require('path');
... ...
router.get('*', function(req, res) {
res.sendfile('./views/index.html'); // works, but a deprecation warning
// res.sendFile('index.html', { root: path.join(__dirname, 'views') }); does not work
});
It is really confusing...
If it's a single page mean application, then you only need to start express with static and put index.html in static/ dir :
Project layout
static/
index.html
server.js
server.js
'use strict';
var express = require('express');
var app = express();
app.use(express.static('public'));
var server = app.listen(8888, function () {
console.log("Server started. Listening on port %s", server.address().port);
});
Now you can call http://localhost:8888/#home
It looks like a problem with the path. Your index.html is located at public/htmls/index.html and views/index.html. Your root option in res.sendFile should be __dirname+/public/htmls/ or __dirname+/views
In your code, you are using the path:
res.sendFile('index.html', { root: __dirname });
Your app.js would be in the project root where you have public directory alongside at the same level. Based on your rootoption in res.sendFile, you would have to place index.html at the same level as your app.js.
You should change the root path in res.sendFile. Use:
res.sendFile('index.html', { root: path.join(__dirname, 'public', 'htmls') });
OR
res.sendFile('index.html', { root: path.join(__dirname, 'views') });
The above root is based on the path that you've mentioned in your question.
Here's the link to the docs:
http://expressjs.com/en/api.html#res.sendFile
Your no default engine error is probably because you have commented the line where you set the view engine to ejs but still have existing ejs views. Uncommenting that line with the root path change should solve your issue.
Do not serve static content from an application server.
Use a web server for that, and in production, a content delivery network like Akamai.
A content delivery network will charge you per bandwidth (e.g: 10 cents per Terabyte). Serving the equivalent of 10 cents in Akamai can cost you thousands of dollars using cloud instances.
In addition to that, your servers will have unnecessary load.
If you absolutely have to serve static content from your application servers, then put a reverse proxy cache like nginx, varnish or squid in front of your server. But that will still be very cost inefficient. This is documented in the express website.
This is common practice in every Internet company.

How to set the default views folder in express?

I am having a lot of trouble getting my routes to direct correctly.
This is my code:
in express.js
app.set('views', config.root + '/app/views')
app.set('view engine', 'jade')
in routes.js
module.exports = function (app) {
// home route
app.get('/', home.index)
}
I keep on getting an error saying that home is not defined, even though I believe that I set the views correctly. Any idea on what the mistake may be.
If it helps, my file structure for my views is under app/views and my config files are config/express.js and config/routes.js
In your routes.js you have not defined home so you will certainly get undefined error. Views does not have anything to do with request handler.
You will use res.render inside your request handler to render the view. Please look in to express.js guide to get started.
Are you trying to do this?
module.exports = function (app) {
// home route
app.get('/', function(req, res){
res.render('home');
});
}
If your request handlers are in separate module you can do this:
if your home request handler is home.js in same path as routes.js you can do following.
Your route.js file
var home = require('./home');//path to your home request handler module
module.exports = function (app) {
// home route
app.get('/', home.index)
}
Your home.js file
exports.index = function(req, res){
res.render('home/index');
}
It will expect you have index.jade view file in your /app/views/home folder.

Node.js catch-all route with redirect always renders index page with Angular regardless of url

Essentially when I use a catch-all route and use res.redirect('/') regardless of the url I enter it will always render the index/home page (ie Angular does not seem to 'see' the full url) however if I place res.render('index') in the catch-all route everything works fine. I don't want repeat code and redirecting to '/' should work, I have probably made a stupid mistake somewhere here and any help would be much appreciated!
Angular routing:
app.config(function ($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/',
{
templateUrl: 'partials/home.jade'
})
.when('/about',
{
templateUrl: 'partials/about.jade'
})
.otherwise( {redirectTo: '/'});
});
This will correctly render the about page when entering site-address/about:
app.get('/', function (req, res) {
res.render('index');
});
app.get('/partials/:name', function (req, res) {
res.render('partials/' + req.params.name);
});
app.get('*', function (req, res) {
res.render('index');
});
This will always just show the index page:
app.get('/', function (req, res) {
res.render('index');
});
app.get('/partials/:name', function (req, res) {
res.render('partials/' + req.params.name);
});
app.get('*', function (req, res) {
res.redirect('/');
});
Configuration if it helps:
// Configuration
app.configure(function () {
app.set('port', process.env.PORT || 1337);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(path.join(__dirname, 'public')));
app.use(app.router);
});
This is by design.
When you use res.redirect('/'), Node/Express is sending an HTTP redirect, which will change the URL in the browser, thus when your index template is rendered, and when the angular code is run, the URL is /, regardless of what the user entered (the whole point of the redirect).
When you omit the redirect and just send the template as a response, NodeJs responds with an HTTP 200 (success) along with HTML content. Since the URL didn't change, when your application runs, the angular routing properly routes.
EDIT TO ADD: Address Comment
Rather than have two routes render the same template, I would get rid of the / route all together, and just have the catch-all render the index template, which will then turn control over to the Angular router.
Otherwise, I would consider splitting your routes conceptually: All your application routes are specifically sent to angular router, and you render static routes via nodejs, and use your catch all to render a more appropriate page for a missing or unknown resource (more helpful for your users).
You can use regex-like languages to specify a single handler:
app.get('/()|(about)|(contact)/',function(req,res) {/* handle */});
For folder structure:
root
-web.js
-dist/index.html
-dist/rest of the page
Just paste the following snippet to your web.js
nodeApp.use('/', express.static(__dirname + '/dist'));
nodeApp.get('/[^\.]+$', function(req, res){
res.set('Content-Type', 'text/html')
.sendfile(__dirname + '/dist/index.html');
});
I have a problem where when I use the catch-all routing, all of my static assets (stylesheets, javascript files etc) don't load:
app.use(app.router);
app.use(express.static(path.join(__dirname, frontend.app)));
// when this is removed, I can load static assets just fine
app.get('*', function(req, res){
res.render('main');
});
When the app.get('*', ...) part is removed, I can load the static assets just fine (i.e. I can type in 'examplejavascript.js' and see the javascript file. When it is there however, express catches the assets.
$locationProvider.html5Mode(true);
are you sure your requests are hitting the nodejs server. using this means, angularjs will try to search for urlmapping inside browser.
try by commenting
$locationProvider.html5Mode(true);

Why do I to have prefix all my route handlers

I'm creating an application using Expressjs running on node under IISNode (i.e on windows).
I start by setting things up very much like all the expressjs examples I've seen:
backend.configure(function() {
backend.register('html', {
compile: function(str, options) {
return function(locals) {
return str;
};
}
});
backend.set('views', __dirname + '/views');
backend.set('view engine', 'html');
backend.set('view options', {
layout: false
});
backend.use(express.bodyParser());
backend.use(backend.router);
backend.use(express.static(__dirname + '/public'));
});
Lets say my site is running at localhost://mysite. I'm having to create all my route handlers as follows.
backend.get('/mysite/index', function(req, res, next) {
return res.render('index');
});
i.e. I'm having to prefix them all with "mysite". Non of the examples I've seen require this. Is this something to do with IISNode or something else I haven't configured?
I was looking for this feature but for API routes, not for static files. What I did was that when I initialized the router, I added the mount path. So my configuration looks like this
//Default configuration
app.configure(function(){
app.use(express.compress());
app.use(express.logger('dev'));
app.set('json spaces',0);
app.use(express.limit('2mb'));
app.use(express.bodyParser());
app.use('/api', app.router); // <---
app.use(function(err, req, res, callback){
res.json(err.code, {});
});
});
Notice the '/api' when calling the router
Out of the box, this is indeed how you have to do it. You could look into express-resource, enabling resourceful routing - but that comes with it's own caveats, at least when it comes to route-specific middleware.
If it's just one path, I think you can handle that with app.set("basepath", "mysite").
To avoid having to modify your express app when deploying in IIS using iisnode, you need to deploy to the root of the IIS WebSite rather than a virtual directory under a site.

Resources