Node.js + Express + MongoDB 实战 TodoList

常用链接

1.介绍

  • express

    • 基于 Node.js 的 web 框架
    • 用于快速搭建网站和应用,如博客、商场、聊天室、为前端提供 API
    • 热门、健全、简单、少走弯路
    • 简单路由系统
    • 集成模版引擎
    • 中间件系统
  • 快速开始

    • npm init -y 默认模式生成 package.json
    • npm install --save express 安装框架
    • npm install -g nodemon 方便调试,nodemon xxx 启动应用
1
var express = require('express')
2
3
var app = express()
4
5
app.get('/', function (req, res) {
6
    res.send('this is homepage')
7
})
8
9
app.listen(3000)

2.请求与响应

1
res.send(new Buffer('whoop'));
2
res.send({ some: 'json' });
3
res.send('<p>some html</p>');
4
res.status(404).send('Sorry, we cannot find that!');
5
res.status(500).send({ error: 'something blew up' });
6
7
res.json({ user: 'tobi' });
8
res.status(500).json({ error: 'message' });
9
10
req.ip
11
// => "127.0.0.1"
12
13
// GET /search?q=tobi+ferret
14
req.query.q
15
// => "tobi ferret"
16
17
// example.com/users?sort=desc
18
req.path
19
// => "/users"

3.路由参数

  • 路由参数是动态的
1
// http://127.0.0.1:3000/profile/1/user/able
2
app.get('/profile/:id/user/:name', function (req, res) {
3
    console.dir(req.params) // 显示属性  { id: '1', name: 'able' }
4
    res.send("You requested " + req.params.id + req.params.name)
5
})
  • 路由参数支持正则表达式
1
app.get('/ab?cd', function (req, res) {
2
    res.send('ab?cd')
3
})

4.查询字符串

1
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
2
req.query.order
3
// => "desc"
4
5
req.query.shoe.color
6
// => "blue"
7
8
req.query.shoe.type
9
// => "converse"
1
// http://127.0.0.1:3000/?find=hot
2
app.get('/', function (req, res) {
3
    console.dir(req.query) // => { find: 'hot' }
4
    res.send('home page: ' + req.query.find)
5
})

5.POST请求和postman工具

  • 使用 body-parser 包,处理 post 请求

  • postman 工具,用来图形化模拟浏览器发送各种请求

  • POST 提交数据方式

    • HTTP/1.1 协议规定的 HTTP 请求方法有 OPTIONSGETHEADPOSTPUTDELETETRACECONNECT 这几种
    • POST 一般用来向服务端提交数据
    • application/x-www-form-urlencoded 普通表单提交
    • multipart/form-data 可以上传文件的表单
1
var bodyParser = require('body-parser')
2
// create application/json parser
3
var jsonParser = bodyParser.json()
4
// 使用中间件,在请求和响应中间处理 create application/x-www-form-urlencoded parser
5
var urlencodedParser = bodyParser.urlencoded({ extended: false })
6
7
app.post('/', urlencodedParser, function (req, res) {
8
    console.dir(req.body)
9
    res.send('ok')
10
})
11
12
app.post('/upload', jsonParser, function (req, res) {
13
    console.dir(req.body)
14
    res.send('ok')
15
})

6.上传文件

  • Multer 包 处理上传文件

    Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files.

  • 安装 npm install --save multer

  • 基于express+multer的文件上传

  • 上传文件的表单需要指定 enctype="multipart/form-data"

  • postman 上传文件,post body form-data

1
// form.html
2
<form action="/upload" method="post" enctype="multipart/form-data">
3
    <h2>上传logo图片</h2>
4
    <input type="file" name="logo">
5
    <input type="submit" value="提交">
6
</form>
1
// 创建目录,上传文件
2
var createFolder = function (folder) {
3
    try {
4
        fs.accessSync(folder);
5
    } catch (e) {
6
        fs.mkdirSync(folder);
7
    }
8
};
9
10
var uploadFolder = './upload/';
11
12
createFolder(uploadFolder);
13
14
var storage = multer.diskStorage({
15
    destination: function (req, file, cb) {
16
        cb(null, uploadFolder);
17
    },
18
    filename: function (req, file, cb) {
19
        cb(null, file.originalname);
20
    }
21
});
22
var upload = multer({ storage: storage });
23
24
app.get('/form', function (req, res) {
25
    var form = fs.readFileSync('./form.html', { encoding: "utf8" })
26
    res.send(form)
27
})
28
29
app.post('/upload', upload.single('logo'), function (req, res) {
30
    console.dir(req.file); // 列出文件的所有属性
31
    res.send({ 'ret_code': 0 })
32
})

7.模版引擎介绍

  • 直接使用res.sendFile(__dirname + '/form.html')响应网页
1
app.get('/form', function (req, res) {
2
    // var form = fs.readFileSync('./form.html', { encoding: "utf8" })
3
    // res.send(form)
4
    res.sendFile(__dirname + '/form.html')
5
})
  • 模版引擎 EJS
    • npm install ejs --save
    • 模版文件扩展名 .ejs
    • ejs 模版的Tags 特殊,非对称的,有前面和后面的,如 %> Plain ending tag
1
app.get('/form/:name', function (req, res) {
2
    var person = req.params.name
3
    res.render('form', { person: person })
4
})
5
6
// views/form.ejs
7
<h1><%= person %></h1>
8
// http://127.0.0.1:3000/form/able
9
// 输出 able
  • 将模板引擎用于 Express
    • 在 Express 可以呈现模板文件之前,必须设置以下应用程序设置
    • views:模板文件所在目录。例如:app.set(‘views’, ‘./views’) 默认
    • view engine:要使用的模板引擎。例如:app.set(‘view engine’, ‘ejs’)

8.使用模版引擎

1
app.get('/form/:name', function (req, res) {
2
    // var person = req.params.name
3
    var person = { age: 29, job: 'CEO', hobbies: ['eating', 'coding', 'finshing']}
4
    res.render('form', { person: person })
5
})
6
7
app.get('/about', function (req, res) {
8
    // var person = req.params.name
9
    res.render('about')
10
})
1
<%- include('particals/header.ejs') -%>  // 引用模版
2
    <h1><%= person %></h1>
3
    <h1><%= person.age %></h1>
4
    <h2>hobbies</h2>
5
    <ul>  //遍历数组
6
        <% person.hobbies.forEach(function(item){ %>
7
            <li>
8
                <%= item %>
9
            </li>
10
        <% }) %>
11
    </ul>

9.中间件介绍

  • 中间件 (middleware)
    • Express 是一个路由和中间件 Web 框架
    • Express 应用程序基本上是一系列中间件函数调用
    • 中间件介于 请求对象 (req)、响应对象 (res) 中间
    • 可以有多个中间件
    • 下一个中间件函数通常由名为 next 的变量来表示
    • 如果当前中间件函数没有结束请求/响应循环那么它必须调用 next(),以将控制权传递给下一个中间件函数。否则,请求将保持挂起状态。
  • 中间件作用
    • 对请求和响应对象进行更改
    • 结束请求或响应循环
    • 调用堆栈中的下一个中间件函数
1
// 没有路径的中间件函数, 每次收到请求时执行该函数。
2
app.use(function (req, res, next) {
3
    console.log('first middleware')
4
    next() // 没有响应请求,需要将控制权传递给下一个中间件函数
5
    console.log('first middleware after next')
6
})
7
8
// 安装在某个路径的中间件函数
9
app.use('/m', function (req, res, next) {
10
    console.log('second middleware')
11
    res.send('ok')
12
})
13
14
// app.get('/m', function (req, res, next) {
15
//     res.send('ok')
16
// })
  • 内置中间件
    • Express 中唯一内置的中间件函数是 express.static
    • 负责提供 Express 应用程序的静态资源
    • app.use(express.static('public')); 指定静态资源根目录
    • app.use('static', express.static('public')); 前缀目录static/a.png

10.路由中间件

  • 路由器层中间件绑定到 express.Router() 的实例
  • 分离路由到子文件目录中,最上次只调用,总分路由
1
// server.js
2
var indexRouter = require('./routes/index')
3
var usersRouter = require('./routes/users')
4
5
app.use('/', indexRouter)
6
app.use('/users', usersRouter)
7
8
// users.js
9
var express = require('express')
10
11
var router = express.Router()
12
// 这里注意,因为前面路由匹配到/users了,这里直接时根即可,二级目录
13
router.get('/', function (req, res, next) {
14
    res.send('users')
15
})
16
17
module.exports = router

11.项目实践 part 1 项目搭建

  • express-todolist 实践项目
    • npm init -y 初始化 package.json
    • npm install --save express body-parser ejs 安装包

12.项目实践 part 2 Controller

  • 新建 controllers 文件夹,单独存放控制器
1
//app.js
2
var express = require('express')
3
var todoController = require('./controllers/todoController')
4
5
var app = express()
6
7
app.set('view engine', 'ejs')
8
9
// 指定public目录下为静态文件根目录
10
app.use(express.static('public'))
11
12
todoController(app)
13
14
app.listen(3000)
15
16
console.log('listening to port 3000')
17
18
// todoController.js
19
module.exports = function (app) {
20
    app.get('/todo', function (req, res) {
21
    })
22
23
    app.post('/todo', function (req, res) {
24
    })
25
26
    app.delete('/todo', function (req, res) {
27
    })
28
}

13.项目实践 part 3 实现页面

  • 新建 views 文件夹,存放模版页面,todo.ejs

  • 使用 BootCDN 在线免费 jQuery 库

    • https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js

14.项目实践 part 4 实现功能

  • body-parser 处理 post 请求
  • ajax 异步处理提交和删除
1
// 显示添加表单和取出内容
2
<div id="todo-table">
3
    <form action="">
4
        <input type="text" name="item" placeholder="Add new item..." required />
5
        <button type="submit">ADD Item</button>
6
    </form>
7
    <ul>
8
        <% todos.forEach(function (todo) { %>
9
            <li><%= todo.item %></li>
10
        <% }) %>
11
    </ul>
12
</div>
1
var bodyParser = require('body-parser')
2
var urlencodeParser = bodyParser.urlencoded({ extended: false})
3
var data = [{item: 'get milk'}, {item: 'walk dog'}, {item: 'coding a'}]
4
5
module.exports = function (app) {
6
    app.get('/todo', function (req, res) {
7
        res.render('todo', { todos: data })
8
    })
9
10
    app.post('/todo', urlencodeParser, function (req, res) {
11
        data.push(req.body)
12
        res.json(data) // 回复结束响应,可以回复其它的
13
    })
14
15
    app.delete('/todo/:item', function (req, res) {
16
        data = data.filter(function (todo) { // 返回为true的内容
17
           return todo.item.replace(/ /g, "-") !== req.params.item
18
        })
19
        res.json(data)
20
        console.log(data)
21
    })
22
}
1
// ajax 处理点击提交 和 删除,异步处理
2
$(document).ready(function() {
3
4
    $('form').on('submit', function(event) {
5
        event.preventDefault();
6
        var item = $('form input');
7
        var todo = { item: item.val().trim() };
8
9
        $.ajax({
10
            type: 'POST',
11
            url: '/todo',
12
            data: todo,
13
            success: function(data) {
14
                //do something with the data via front-end framework
15
                location.reload();
16
            }
17
        });
18
19
        return false;
20
21
    });
22
23
    $('li').on('click', function() {
24
        var item = $(this).text().trim().replace(/ /g, "-");
25
        $.ajax({
26
            type: 'DELETE',
27
            url: '/todo/' + item,
28
            success: function(data) {
29
                //do something with the data via front-end framework
30
                location.reload();
31
            }
32
        });
33
    });
34
});

15.项目实践 part 5 MongoDB 和 mLab

  • 使用 MongoDB 持久化数据

    • nosql 非关系型的数据库,没有行列的概念,存储的 json 格式数据,用js很方便读取
    • MongoDB 概念解析
    • collection 数据库表/集合
    • document 数据记录行/文档
    • primary key 主键,MongoDB自动将_id字段设置为主键
  • 使用线上免费 MongoDB 数据库 mLab

    • Database-as-a-Service features
    • MongoDB on AWS, Azure, or Google. It’s this easy.
    • 注册,创建数据库,创建数据库用户
    • shell 连接 mongo ds020208.mlab.com:20208/todos -u <dbuser> -p <dbpassword>
    • URI mongodb://<dbuser>:<dbpassword>@ds020208.mlab.com:20208/todos

16.项目实践 part 6 Mongoose

  • mongoose 用来操作数据库
    • a MongoDB object modeling tool designed to work in an asynchronous environment.
    • npm install mongoose --save
    • 安装,连接,定义 Schema、model ,规定数据类型一致
1
const mongoose = require('mongoose')
2
mongoose.connect('mongodb://able8:xx@ds020208.mlab.com:20208/todos')
3
4
// Schema 模式,规定数据类型
5
var todoSchema = new mongoose.Schema({
6
    item: String // 字段名,字符串
7
})
8
//对应数据库中的表
9
var Todo = mongoose.model('Todo', todoSchema)
10
// 添加一条数据
11
var itemOne = Todo({ item: 'buy flowers'}).save(function (err) {
12
    if (err) throw err
13
    console.log('item saved')
14
})

17.项目实践 part 7 保持数据到 MongoDB

  • 操作数据,读取,添加,删除
  • 实践测试 mLab 国内访问太慢了, 简单测试还可以
  • 其他可选包 mongolass
1
app.get('/todo', function (req, res) {
2
    Todo.find({}, function (err, data) {
3
        if (err) throw err
4
        res.render('todo', { todos: data })
5
    })
6
})
7
8
app.post('/todo', urlencodeParser, function (req, res) {
9
    var itemOne = Todo(req.body).save(function (err, data) {
10
        if (err) throw err
11
        res.json(data)
12
    })
13
})
14
15
app.delete('/todo/:item', function (req, res) {
16
    // data = data.filter(function (todo) { // 返回为true的内容
17
    //    return todo.item.replace(/ /g, "-") !== req.params.item
18
    // })
19
    Todo.find({item: req.params.item.replace(/ /g, '-')}).remove(function (err, data) {
20
        if (err) throw err
21
        res.json(data)
22
    })
23
})