Nodejs 快速开发Web产品

Who am I?我是谁?

Alibaba Data EDP

阿里巴巴数据平台EDP,花名@苏千

Chinese nodejs community: cnodejs.org


Github: @fengmk2

Blog: http://fengmk2.github.com

Twitter: @fengmk2

Weibo: @Python发烧友 , @FaWave

什么是Web开发

Web开发

Why Nodejs?

IO密集型

* 磁盘IO:读写文件

* 网络IO:数据库,Cache,Services

* 进程:进程通信,进程调用

Nodejs的核心关键词

about nodejs

* V8

* Event driven

* Non-blocking IO

V8有多快?

fibonacci

高楼平地起: Hello World

helloworld.js

var http = require('http');
http.createServer(function (req, res) {
  console.log('%s %s : %j', req.method, req.url, req.headers);
  req.on('end', function () {
    res.writeHeader(200, { 'Content-Type': 'text/html' });
    res.end('Hello QCon Hangzhou');
  });
}).listen(1984);

Event driven: req.on('end', function () {})

看看得到什么

hello world result

console.log()

$ node helloworld.js 

GET / : {"host":"localhost.cnodejs.org:1984","connection":"keep-alive","cache-control":"max-age=0","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","accept-encoding":"gzip,deflate,sdch","accept-language":"zh-CN,zh;q=0.8,en;q=0.6,en-US;q=0.4","accept-charset":"GBK,utf-8;q=0.7,*;q=0.3"}

GET /favicon.ico : {"host":"localhost.cnodejs.org:1984","connection":"keep-alive","accept":"*/*","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4","accept-encoding":"gzip,deflate,sdch","accept-language":"zh-CN,zh;q=0.8,en;q=0.6,en-US;q=0.4","accept-charset":"GBK,utf-8;q=0.7,*;q=0.3"}

说笑吧,太简单了吧

Simple todo

使用 Mongodb 存储,快速实现最简单的两个功能:

* 添加任务

* 显示任务

* 完成任务

目录结构

|- app.js
|- config.js
|- routes.js
|- package.json
|- views/
 |- layout.html
 |- index.html
|- public/
 |- images/
 |- styles/
|- controllers/
 |- home.js
 |- task.js
|- models/
 |- db.js
 |- task.js
|- node_modules/

package.json

使用npm init 生成项目的 package.json

$ npm init

使用强大的第三方模块加速开发效率

依赖模块:

  "dependencies": {
    "connect": "2.6.0", // web server, static files hosting
    "urlrouter": "0.2.3", // url routing
    "connect-render": "0.1.7", // ejs template engine helper
    "mongoskin": "0.4.4" // mongodb client
  }

主页面效果

HTML + CSS,服务器端使用 ejs 模板引擎渲染

index page

主页面 URL Routing

URL路由简单明了

var home = require('./controllers/home');
// HTTP GET / => home controller
app.get('/', home);

Home Controller

module.exports = function home(req, res, next) {
  res.render('index.html', {
    tasks: [] // 稍后会增加数据库逻辑
  });
};

index.html 模板

<div class="box todos">
  <h2 class="box">待办事项</h2>
  <ul>
    <% for (var i = 0; i < tasks.length; i++) {
      var task = tasks[i];
      var classname = task.finished ? 'class="finished"' : '';
    %>
      <li <%- classname %>>
        <% if (!task.finished) { %>
          <%= task.title %>
          &nbsp;
          <a href="/task/<%- task._id %>/finish">完成</a>
        <% } else { %>
          <del><%= task.title %></del>
          &nbsp;
          <a href="/task/<%- task._id %>/unfinish">恢复</a>
        <% } %>
      </li>
    <% } %>
  </ul>
</div>

静态文件服务

直接使用 connect 的 static 中间件模块

app.use('/public', 
  connect.static(path.join(__dirname, 'public')));

CSS,图片,客户端脚本

<link href="/public/styles/reset.css" rel="stylesheet" type="text/css" />
<link href="/public/styles/index/style.css" rel="stylesheet" type="text/css" />

添加任务

URL路由配置

var task = require('./controllers/task');

app.post('/task', task.add);

HTML模板及CSRF保护

  <div class="box post">
    <h2>新增</h2>
    <form action="/task" method="post" id="post_new">
      <input type="hidden" name="_csrf" value="<%- _csrf %>" />
      <p><input type="text" name="title" class="long_txt" /></p>
      <p><input type="submit" class="submit" value="添加" /></p>
    </form>
  </div>

添加任务 controller 实现代码

var Task = require('../models').Task;

exports.add = function (req, res, next) {
  var title = req.body.title;
  var task = {
    title: title, 
    finished: 0, 
    created_at: new Date()
  };
  Task.insert(task, function (err, item) {
    if (err) {
      return next(err);
    }
    res.writeHeader(302, {
      Location: '/'
    });
    res.end();
  });
};

在主页面上显示刚才添加的任务

var Task = require('../models').Task;

module.exports = function home(req, res, next) {
  // 按未完成的排前面,然后再按时间倒序显示
  var options = { 
    sort: [ [ 'finished', 'asc' ], [ '_id', 'desc' ] ] 
  };
  Task.findItems({}, options, function (err, tasks) {
    if (err) {
      return next(err);
    }
    res.render('index.html', {
      tasks: tasks
    });
  });
};

添加任务效果

add task

完成任务

URL Routing

// GET /task/50843cf924438a2dfa000001/finish
app.get('/task/(:id)/finish', task.finish);

controller 实现代码

exports.finish = function (req, res, next) {
  var tid = req.params.id; // mapping from url
  var task = { finished: 1, updated_at: new Date() };
  Task.updateById(tid, { $set: task }, function (err, item) {
    if (err) {
      return next(err);
    }
    res.writeHeader(302, {
      Location: '/'
    });
    res.end();
  });
};

整体效果

todo2

Talk is cheap

删除任务,编辑任务,用户系统...,剩余的功能,等你来完成。

“自己动手,丰衣足食”

fork github.com/fengmk2/nodejs-web-dev, then do it.

应用展示

淘宝指数

shu

数据魔方

mofang

内部 NPM Web

npmweb

CNodejs 社区

cnode

/ppt/nodejs-web-dev.html

web.emit('Thanks')
&&
console.log('end');

/

#