Node.js Express 3.0 development Basic

nodejs-2

Introduction

Through Node.js, JavaScript become ideal tool for server applications. And through jQuery, JavaScript is excellent tool for browser development. Recently we learnt about the Express 3.0 in Node.js. The book we were referring to was talking about Express2.x, so we were meeting some difficulties when learning Express3.0. We would love to share with you how we overcame them.

<<Node.js toolkit>> ©2014, Conan Zhang and Vivian S. Zhang. All rights Reserved. The corresponding post written in Chinese can be found at Conan Zhang’s blog. Please contact [email protected] if you are interested to publish it in English.

<<Node.js toolkit>> will introduce you to use Javescript as server side script and use node.js framework to develop website. Nodejs framework is based on the V8 engine which is the fastest Javascript engine. Chrome browser is also based on V8. It is very smooth even when you open 20 to 30 pages simultaneously. Node.js standard web development framework, Express, can help us quickly build web sites. Developing website by node.js is more efficient than doing it by PHP and require less steep learning curve. It is ideal for building small sites and personalized web sites. We want to introduce you a lot of handy tools to reduce your workload and make elegant and beautiful site easily.

Content

We will focus on the Express 3.0 Framework, and also related things as Mongoose, Ejs, Bootstrap.

  1. Build a project
  2. Directory Structure
  3. Express3.0 Configuration
  4. Ejs template
  5. Bootstrap Framework
  6. Routing Function
  7. Use of Session
  8. Page Notification
  9. Page Visit Control

Install MongoDB

For Mac user, please refer to here.  For windows user, please refer to here.

Develop Environment

Windows 7 Ultimate 64bit

MongoDB: v2.4.3

Tue May 14 09:24:50.118 [initandlisten] MongoDB starting : pid=1716 port=27017 dbpath=./data 64-bit host=PC201304202140
Tue May 14 09:24:50.119 [initandlisten] db version v2.4.3
Tue May 14 09:24:50.119 [initandlisten] git version: fe1743177a5ea03e91e0052fb5e2cb2945f6d95f
Tue May 14 09:24:50.119 [initandlisten] build info: windows sys.getwindowsversion(major=6, minor=1, build=7601, platform=2, service_pack='Service Pack 1') BOOST_LIB_VERSION=1_49
Tue May 14 09:24:50.119 [initandlisten] allocator: system
Tue May 14 09:24:50.119 [initandlisten] options: { dbpath: "./data" }
Tue May 14 09:24:50.188 [initandlisten] journal dir=./data\journal
Tue May 14 09:24:50.189 [initandlisten] recover : no journal files present, no recovery needed
Tue May 14 09:24:50.441 [initandlisten] preallocateIsFaster=true 3.26
Tue May 14 09:24:50.778 [initandlisten] preallocateIsFaster=true 5.88
Tue May 14 09:24:51.827 [initandlisten] waiting for connections on port 27017
Tue May 14 09:24:51.827 [websvr] admin web console waiting for connections on port 28017

Node.js: v0.10.5, npm 1.2.19

node -v
v0.10.5
npm -v
1.2.19

1. Build a project

Entering into the project directory

cd D:\workspace\project

Globally install express, and it is installed as a command in the system

npm install -g express

Check the version of express

express -V
3.2.2

Create the project with command express . It supports Ejs.

D:\workspace\project>express -e nodejs-demo

create : nodejs-demo create : nodejs-demo/package.json
create : nodejs-demo/app.js
create : nodejs-demo/public
create : nodejs-demo/public/javascripts
create : nodejs-demo/public/images
create : nodejs-demo/public/stylesheets
create : nodejs-demo/public/stylesheets/style.css
create : nodejs-demo/routes
create : nodejs-demo/routes/index.js
create : nodejs-demo/routes/user.js
create : nodejs-demo/views
create : nodejs-demo/views/index.ejs

install dependencies:

$ cd nodejs-demo && npm install

run the app:

$ node app

Install the dependencies according to the notification

cd nodejs-demo && npm install

[email protected] node_modules\express
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected] ([email protected])
└── [email protected] ([email protected], [email protected], [email protected])

Template project has been successfully built. Start the template project.

D:\workspace\project\nodejs-demo>node app.js
Express server listening on port 3000

Local port 3000 is opened, visit localhost:3000 via your browser.

If we run the program by node, we need to restart for every change in the code. There’s a tool called supervisor, it will restart automatically after we change the code. It will save our time.

npm install supervisor

We start the service again

D:\workspace\project\nodejs-demo>supervisor app.js

DEBUG: Running node-supervisor with
DEBUG: program 'app.js'
DEBUG: --watch '.'
DEBUG: --ignore 'undefined'
DEBUG: --extensions 'node|js'
DEBUG: --exec 'node'

DEBUG: Starting child process with 'node app.js'
DEBUG: Watching directory 'D:\workspace\project\nodejs-demo' for changes.
Express server listening on port 3000

2. Directory Structure

D:\workspace\project\nodejs-demo>dir

2013/05/14 09:42 877 app.js
2013/05/14 09:48 <DIR> node_modules
2013/05/14 09:42 184 package.json
2013/05/14 09:42 <DIR> public
2013/05/14 09:42 <DIR> routes
2013/05/14 09:42 <DIR> views

Introductions to directories:

  • node_modules: Store all the dependent libraries(every project manage its own dependencies which is different from Maven and Gradle).
  • package.json: Project dependencies configuration and developer information
  • app.js: Application starting file
  • public: Static files(css,js,img)
  • routes: Routes files(C in MVC, controller)
  • Views: Page files(Ejs template)

3. Express3.0 Configuration

Open file app.js

/**
* 模块依赖
*/
var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path');

var app = express();

//环境变量
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// 开发模式
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}

// 路径解析
app.get('/', routes.index);
app.get('/users', user.list);

// 启动及端口
http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); });

4. Ejs template

Make Ejs template file use files with extension as ‘html’. We modify app.js

app.engine('.html', ejs.__express);
app.set('view engine', 'html');// app.set('view engine', 'ejs');

After our modification, variable ejs is undefined and supervisor will keep throwing errors.

ReferenceError: ejs is not defined
at Object. (D:\workspace\project\nodejs-demo\app.js:17:21)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:901:3
DEBUG: Program node app.js exited with code 8

Add variable ejs in app.js

var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path')
, ejs = require('ejs');

Visit localhost:3000 and meet Error

Error: Failed to lookup view "index"
at Function.app.render (D:\workspace\project\nodejs-demo\node_modules\express\lib\application.js:495:17)
at ServerResponse.res.render (D:\workspace\project\nodejs-demo\node_modules\express\lib\response.js:756:7)
at exports.index (D:\workspace\project\nodejs-demo\routes\index.js:7:7)
at callbacks (D:\workspace\project\nodejs-demo\node_modules\express\lib\router\index.js:161:37)
at param (D:\workspace\project\nodejs-demo\node_modules\express\lib\router\index.js:135:11)
at pass (D:\workspace\project\nodejs-demo\node_modules\express\lib\router\index.js:142:5)
at Router._dispatch (D:\workspace\project\nodejs-demo\node_modules\express\lib\router\index.js:170:5)
at Object.router (D:\workspace\project\nodejs-demo\node_modules\express\lib\router\index.js:33:10)
at next (D:\workspace\project\nodejs-demo\node_modules\express\node_modules\connect\lib\proto.js:190:15)
at Object.methodOverride [as handle] (D:\workspace\project\nodejs-demo\node_modules\express\node_modules\connect\lib\middleware\methodOverride.js:37:5)
GET / 500 26ms

Rename views/indes.ejs as views/index.html, and we will successfully visit localhost:3000.

5. Add Bootstrap Framework

Actually it just means copying js and css files into corresponding directories. There are four files.

Copy to public/stylesheets

bootstrap.min.css
bootstrap-responsive.min.css

Copy to public/javascripts

bootstrap.min.js
jquery-1.9.1.min.js

Next, we split index.html into 3 parts: header.html, index.html and footer.html.

header.html: Head of html page index.html: Content of html page footer.html: Bottom of page

header.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><%=: title %></title>
<!-- Bootstrap -->
<link href="/stylesheets/bootstrap.min.css" rel="stylesheet" media="screen">
<!-- <link href="css/bootstrap-responsive.min.css" rel="stylesheet" media="screen"> -->
</head>
<body screen_capture_injected="true">

index.html

<% include header.html %>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
<% include footer.html %>

Note: In express3.0, use include to embed Ejs into other pages, this is different from express2.x.

footer.html

<script src="/javascripts/jquery-1.9.1.min.js"></script>
<script src="/javascripts/bootstrap.min.js"></script>
</body>
</html>

We can access localhost:3000.

We have now successfully adapted the template of Ejs here to split public header and footer from the page file. Also, we have introduced bootstrap framework. We will see it later.

6. Routing Function

Let’s design our service for login.

Visit Path: /, Page: index.html, No need to login to visit. Visit Path: /home, Page: home.html, Need to login to visit. Visit Path: /login, Page: login.html, Login Page, Automatically jump to home.html with correct username and password Visit Path: /logout, Page: No. Automatically back to index.html after logout

Open file app.js, Add routing configuration.

app.get('/', routes.index);
app.get('/login', routes.login);
app.post('/login', routes.doLogin);
app.get('/logout', routes.logout);
app.get('/home', routes.home);

Note: get is get request, post is post request, all is request for this path.

Open file routes/index.js and add corresponding methods.

exports.index = function(req, res){
res.render('index', { title: 'Index' });
};
exports.login = function(req, res){
res.render('login', { title: '用户登陆'});
};
exports.doLogin = function(req, res){
var user={
username:'admin',
password:'admin'
}
if(req.body.username===user.username &&
req.body.password===user.password){
res.redirect('/home');
}
res.redirect('/login');
};
exports.logout = function(req, res){
res.redirect('/');
};
exports.home = function(req, res){
var user={
username:'admin',
password:'admin'
}
res.render('home', { title: 'Home',user: user});
};

Create two files: views/login.html and views/home.html.

login.html:

<% include header.html %>
<div class="container-fluid">
<form class="form-horizontal" method="post">
<fieldset>
<legend>User Login</legend>
<div class="control-group">
<label class="control-label" for="username">Username</label>
<div class="controls">
<input type="text" class="input-xlarge" id="username" name="username">
</div>
</div>
<div class="control-group">
<label class="control-label" for="password">Password</label>
<div class="controls">
<input type="password" class="input-xlarge" id="password" name="password">
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</fieldset>
</form>
</div>
<% include footer.html %>

login

Note: this is bootstrap framework, isn’t it pretty?
home.html:

<% include header.html %>
<h1>Welcome <%= user.username %>, Welcome! </h1>
<a claa="btn" href="/logout">Logout</a>
<% include footer.html %>

Modify index.html to add link to login.
index.html:
<% include header.html %>
<h1>Welcome to <%= title %></h1>
<p><a href="/login">Login</a></p>
<% include footer.html %>

After this configuration, let’s try it now!
7. Use of Session
From the example, when we process exports.doLogin, we use redirect method to jump to home if username and password are correct.
res.redirect('/home');
When we process exports.home, we user render to render the page and pass object user to the page home.html.
res.render('home', { title: 'Home',user: user});
Why can’t we pass the object user to session in doLogin so that we don’t need to pass it in every page?
Actually it is related to how the server deal with it. Web server of Java is multi-thread model. Every user request will use a thread, and this thread maintains the user’s status. Web server of php use CGI program. But CGI is status-less, therefore it use cookie to maintain user’s status. But there’s not enough information in cookie, therefore if CGI needs to mimic the user session, it needs to generate a session file. So CGI application could achieve session via middle files.
Web server of Node.js is CGI without status. The difference from php is that all the requests are asynchronously answered and return data via callback. If we want to store session data, we need to store it by file, redis or Mongodb.
Next, we are going to illustrate how to store session in Mongodb and pass object after user login.
app.js
var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path')
, ejs = require('ejs')
, SessionStore = require("session-mongoose")(express);
var store = new SessionStore({
url: "mongodb://localhost/session",
interval: 120000
});
....
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.cookieSession({secret : 'fens.me'}));
app.use(express.session({ secret : 'fens.me', store: store, cookie: { maxAge: 900000 } }));
app.use(function(req, res, next){ res.locals.user = req.session.user; next(); });
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

Node: one must notice that order matters in app.js !!
Install dependency session-mongoose:
D:\workspace\project\nodejs-demo>npm install session-mongoose
D:\workspace\project\nodejs-demo\node_modules\session-mongoose\node_modules\mongoose\node_modules\mongodb\node_modules\bson>node "D:\toolkit\nodejs\node_modules\npm\bin\node-gyp-bin\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Microsoft.Cpp.InvalidPlatform.Targets(23,7): error MSB8007: invalid platform of project "kerberos.vcxproj”. The platform is "x64". THe reason you see this message is possibly because you are trying to create project without solution files, and it is oose\node_modules\mongoose\node_modules\mongodb\node_modules\bson\build\bson.vcxproj] [email protected] node_modules\session-mongoose └── [email protected] ([email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected])

There’s an error, but it doesn’t matter. We can access localhost:3000 .
Modify routes/index.js.
Method exports.doLogin:
exports.doLogin = function(req, res){
var user={
username:'admin',
password:'admin'
}
if (req.body.username===user.username &&
req.body.password===user.password){
req.session.user=user;
return res.redirect('/home');
} else {
return res.redirect('/login');
}
};

Method exports.logout:
exports.logout = function(req, res){
req.session.user=null;
res.redirect('/');
};

Method exports.home:
exports.home = function(req, res){
res.render('home', { title: 'Home'});
};

Now session is working, there’s no explicit value pass in exports.home. Instead, we assign new value by res.locals of app.use in app.js.
app.use(function(req, res, next){
res.locals.user = req.session.user;
next();
});

Note: This is the implementation in express3.0 and is different from express2.x. When we assign value , the framework will do the work for us.
8. Page Notification
We’ve just cover the major steps of login, and now we are talking about how to deal with failed login. We want to notice users when they enter wrong username or password.
Open app.js, add res.locals.message
app.use(function(req, res, next){
res.locals.user = req.session.user;
var err = req.session.error;
delete req.session.error;
res.locals.message = ''; if (err) res.locals.message = '<div class="alert alert-error">' + err + '</div>';
next();
});

Modify login.html:
<%- message %>
<% include header.html %>
<div class="container-fluid">
<form class="form-horizontal" method="post">
<fieldset>
<legend>用户登陆</legend>
<%- message %>
<div class="control-group">
<label class="control-label" for="username">用户名</label>
<div class="controls">
<input type="text" class="input-xlarge" id="username" name="username" value="admin">
</div>
</div>
<div class="control-group">
<label class="control-label" for="password">密码</label>
<div class="controls">
<input type="password" class="input-xlarge" id="password" name="password" value="admin">
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">登陆</button>
</div>
</fieldset>
</form>
</div>
<% include footer.html %>

Modify routes/index.js and add req.session.error:
exports.doLogin = function(req, res){
var user={
username:'admin',
password:'admin'
}
if(req.body.username===user.username &&
req.body.password===user.password){
req.session.user=user;
return res.redirect('/home');
} else {
req.session.error='Wrong username or password';
return res.redirect('/login');
}
};

Let’s try it. In localhost:3000/login , we enter wrong username and password: adminfe and 12121.
loginErr
 

9. Page Visit Control

We’ve successfully enable login function. But our website is not safe yet.

Visit localhost:3000/home, it should only be accessible after our login, but now we can visit it even not logged in.

The page throws an error with variable <%= user.username %>.

GET /home?user==a 500 15ms
TypeError: D:\workspace\project\nodejs-demo\views\home.html:2
1| <% include header.html %>
>>
2| <h1>Welcome <%= user.username %>, 欢迎登陆!!</h1>
3| <a claa="btn" href="/logout">退出</a>
4| <% include header.html %>
Cannot read property 'username' of null
at eval (eval at <anonymous> (D:\workspace\project\nodejs-demo\node_modules\ejs\lib\ejs.js:
at eval (eval at <anonymous> (D:\workspace\project\nodejs-demo\node_modules\ejs\lib\ejs.js:
at D:\workspace\project\nodejs-demo\node_modules\ejs\lib\ejs.js:249:15
at Object.exports.render (D:\workspace\project\nodejs-demo\node_modules\ejs\lib\ejs.js:287:
at View.exports.renderFile [as engine] (D:\workspace\project\nodejs-demo\node_modules\ejs\l
at View.render (D:\workspace\project\nodejs-demo\node_modules\express\lib\view.js:75:8)
at Function.app.render (D:\workspace\project\nodejs-demo\node_modules\express\lib\applicati
at ServerResponse.res.render (D:\workspace\project\nodejs-demo\node_modules\express\lib\res
at exports.home (D:\workspace\project\nodejs-demo\routes\index.js:36:8)
at callbacks (D:\workspace\project\nodejs-demo\node_modules\express\lib\router\index.js:161

This is because there’s no user.username parameter. We need to avoid this happening.

Still remember what we mentioned about get, post and all? We are returning to routing configuration and change something.

Modify app.js:

app.all('/login', notAuthentication); app.get('/login', routes.login); app.post('/login', routes.doLogin); app.get('/logout', authentication); app.get('/logout', routes.logout); app.get('/home', authentication); app.get('/home', routes.home);

Access:

  • /: anybody could visit
  • /login: use all to block all the request of visiting /login. We call authentication first and check whether user’s been logged in
  • /logout: use get to block request of visiting /logout. We call notAuthentication and do not check whether user’s been logged in
  • /home: use all to block all the request of visiting /home. We call authentication first and check whether user’s been logged in

Modify app.js, add two methods: authentication and notAuthentication.

function authentication(req, res, next) {
if (!req.session.user) {
req.session.error='Login first';
return res.redirect('/login');
} next();
}
function notAuthentication(req, res, next) {
if (req.session.user) {
req.session.error='Logged in';
return res.redirect('/');
}
next();
}

After this configuration, we will be redirected to /login if we visit /home and not logged in.

loginHome

If you see the same thing on the picture, congratulations! You’ve finished the first step of Express3.0 framework, and also used Ejs, bootstrap and mongoose.