其他章节请看:

前端学习 node 快速入门 系列

报名系统 - [express]

最简单的报名系统:

  • 只有两个页面
  • 人员信息列表页:展示已报名的人员信息列表。里面有一个报名按钮,点击按钮则会跳转到报名页
  • 报名页:用于报名。里面是一个表单,可以输入姓名和年龄,点击保存,成功后会跳转到人员信息列表页

本文主要分 3 部分:

  1. 使用 node 实现这个项目
  2. 介绍 express 相关知识
  3. 使用 express 重写这个项目

Tip: 有将本文分成两篇的打算,因为篇幅有点长;但最后还是决定写在一起,因为更加紧凑。

node 实现

目录如下:

- demo
- public // 存放静态资源
- css
- global.css
- views // 存放模板
- add.html // 报名页
- list.html // 列表页
- index.js // 入口文件
- package.json // PS: 自己安装依赖包

global.css:

body{color:red;}

add.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/submit" method='get'>
<p><input type="text" name='name'></p>
<p><input type="text" name='age'></p>
<p><input type="submit" value='保存'></p>
</form>
</body>
</html>

list.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/public/css/global.css">
</head>
<body>
<p><a href="/add">报名</a></p>
<section>
{{each rows}}
<li>{{$value.name}} {{$value.age}}</li>
{{/each}}
</section>
</body>
</html>

index.js:

// 模拟数据库
const DB = [
{name: 'ph', age: '18'},
{name: 'lj', age: '19'}
]
const http = require('http')
const fs = require('fs')
const template = require('art-template') http.createServer(function(req, res){
const url = req.url
// URL模块的 WHATWG API。Constructor: new URL(input[, base])
// api: https://nodejs.org/dist/v12.18.1/docs/api/url.html#url_constructor_new_url_input_base
const urlObj = new URL(url, `https://${req.headers.host}`)
// 列表页面 list.html
if(url === '/'){
fs.readFile('./views/list.html', (err, data) => {
if (err) throw err;
const ret = template.render(data.toString(), {
rows: DB
});
res.end(ret)
})
// 留言页面 add.html
}else if(url.indexOf('/add') === 0){
fs.readFile('./views/add.html', (err, data) => {
if (err) throw err;
res.end(data)
})
// 提交留言
}else if(urlObj.pathname === '/submit'){
// 插入数据
const row = {}
row.name = urlObj.searchParams.get('name')
row.age = urlObj.searchParams.get('age')
DB.unshift(row);
// 临时重定向
res.statusCode = '302'
res.setHeader('Location', '/');
res.end()
}else if(urlObj.pathname.endsWith('.css')){
fs.readFile('./' + url, (err, data) => {
if (err) throw err;
res.end(data)
})
}else{
res.end('404')
} }).listen(3000)

package.json:

  • 可以先在 demo 路径下执行 npm init -y 来帮助我们生成 package.json 文件
  • 接着执行 npm install art-template 安装插件即可
{
...
"dependencies": {
"art-template": "^4.13.2"
}
}

运行程序:

$ cd demo

// 自行安装依赖包: npm install

// 启动服务 - 前文已介绍笔者使用 nodemon 来代替 node 启动服务
$ nodemon index

浏览器访问 http://localhost:3000/,进入列表页(list.html),页面显示:

报名

ph 18
lj 19

:如果 node 控制台报错,则需要你根据错误提示修改一下,比如你把文件夹 views 一不小心写成了 view。

点击报名,进入报名页面(add.html),显示一个表单,输入名字(pm)和年龄(22),点击保存,则会重定向到人员信息列表页,页面显示:

报名

pm 22
ph 18
lj 19

至此,这个简单的项目就已经完成。

接下来用 express 框架重写该项目之前,我们得先介绍一下 express 相关的知识。

express 基础知识

笔者通过 express 中文网 来介绍 express。这类技术网站称之为 cooking(烹饪) 网站。好比教我们如何烹饪,得先买菜(安装),然后放油、放葱姜蒜,爆炒1分钟...,一步一步告诉我们怎么做,相对比较简单。

进入 express 中文网,导航的菜单如下:

  • 首页
  • 快速入门
    • 安装
    • hello-world
    • 基本路由
    • 静态文件
    • FAQ
    • ...
  • 指南
    • 路由
    • 开发中间件
    • 使用模板引擎
    • 集成数据库
  • API参考手册
  • ...

Tip: 主要介绍 express 重写报名系统需要用到的知识点,更多细节请参考 express 官网

首页

Express - 基于 Node.js 平台,快速、开放、极简的 Web 开发框架。

快速入门 - 安装

$ npm install express

快速入门 - hello-world

const express = require('express')
const app = express()
const port = 3000 app.get('/', (req, res) => {
res.send('Hello World!')
}) app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})

启动服务,访问 http://localhost:3000/ 页面会输出 Hello World!;如果访问其他路径(例如 http://localhost:3000/a),则会以 404 响应。

快速入门 - 基本路由

路由是指确定应用程序如何响应客户端对特定端点的请求,该特定端点是URI(或路径)和特定的HTTP请求方法(GET,POST等)。

语法:app.METHOD(PATH, HANDLER)

以下定义了 4 个路由,请看示例:

app.get('/', function (req, res) {
res.send('Hello World!')
}) app.post('/', function (req, res) {
res.send('Got a POST request')
}) app.put('/user', function (req, res) {
res.send('Got a PUT request at /user')
}) app.delete('/user', function (req, res) {
res.send('Got a DELETE request at /user')
})

快速入门 - 静态文件

利用 Express 托管静态文件。

为了提供诸如图像、CSS 文件和 JavaScript 文件之类的静态文件,请使用 Express 中的 express.static 内置中间件函数。

语法:express.static(root, [options])

如果需要将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放,下面两种方式都可以。

方式1:

app.use(express.static('public'))

// 现在,你就可以访问 public 目录中的所有文件了:

http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/hello.html

方式2:

app.use('/static', express.static('public'))

// 现在,你就可以通过带有 /static 前缀地址来访问 public 目录中的文件了。

http://localhost:3000/static/images/kitten.jpg
http://localhost:3000/static/css/style.css

Tip:提供给express.static函数的路径是相对于您启动节点进程的目录的。 如果从另一个目录运行Express App,则使用要提供服务的目录的绝对路径更为安全:

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

更多关于 __dirname,请看本文 path 模块 章节

快速入门 - FAQ

如何处理 404 响应?

app.use(function (req, res, next) {
res.status(404).send("Sorry can't find that!")
})

如何设置一个错误处理器?

app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})

如何渲染纯 HTML 文件?

不需要!无需通过 res.render() 渲染 HTML。 你可以通过 res.sendFile() 直接对外输出 HTML 文件。 如果你需要对外提供的资源文件很多,可以使用 express.static() 中间件。

指南 - 路由

路由路径匹配 acd 和 abcd:

app.get('/ab?cd', function (req, res) {
res.send('ab?cd')
})

路由参数:

Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }
})

express 提供了一些响应方法,res.end()、res.redirect()、res.render()、res.sendStatus()...,可以将响应发送到客户端,并终止请求-响应周期。 如果没有从路由处理程序中调用这些方法,则客户端请求将被挂起。

指南 - 开发中间件

从接收请求,到发送响应,我们可以加入各种中间件来做一些处理。中间件又可以传给下一个中间件处理。下面我们定义了一个 myLogger 的中间件,请看示例:

var express = require('express')
var app = express() var myLogger = function (req, res, next) {
console.log('LOGGED')
next() // {1}
} // 使用中间件
app.use(myLogger) app.get('/', function (req, res) {
res.send('Hello World!')
}) app.listen(3000)

每次请求,node 都会输出 LOGGED。如果将 next() (行{1})注释,再次请求,页面将一直转圈圈,因为响应被挂起了。

指南 - 使用模板引擎

笔者使用的模板引擎是前文已使用过的 art-template。打开 art-template 官网,点击 Express 菜单就能看到该模板在 express 中使用的方法。请看:

npm install express-art-template

var express = require('express');
var app = express(); // view engine setup
app.engine('art', require('express-art-template')); // {20} app.set('views', path.join(__dirname, 'views')); // routes
app.get('/', function (req, res) {
res.render('index.art', { // {21}
user: {
name: 'aui',
tags: ['art', 'template', 'nodejs']
}
});
});

模板文件默认是 .art,可以改成 .html,只需要将 art(行{20}和行{21}) 改为 html 即可。

指南 - 集成数据库

MongoDB

Mongoose

Tip: 后续将会使用 Mongoose 依赖包来将 MongoDB 数据库加入我们的项目。

API参考手册

由于我的下载的 express 是 4.17.1,所以我参考的 API 是 4.x。

req.body - 包含在请求正文中提交的数据的键值对。 默认情况下,它是未定义的,并且在使用诸如 body-parser 和 multer 之类的 body-parsing 中间件时填充。请看示例:

var express = require('express')

var app = express()

app.use(express.json()) // for parsing application/json
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded app.post('/profile', function (req, res, next) {
console.log(req.body)
res.json(req.body)
})

express 重写

在 node 实现的项目(demo)的基础上,共 3 处变化: add.html、index.js 和 package.json。

1、add.html:method='get' 改为 method='post'

2、index.js:

const path = require('path')
const express = require('express')
const app = express()
// 填充 req.body
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded // view engine setup
app.engine('html', require('express-art-template'));
// 可以通过下面语句更改模板视图的文件夹,默认是 views。
// app.set('views', path.join(__dirname, 'views'));
// 模拟数据库
const DB = [
{name: 'ph', age: '18'},
{name: 'lj', age: '19'}
];
const port = 3000
// 将静态资源对外开放
app.use('/public', express.static('public')) app.get('/', function (req, res) {
res.render('list.html', {
rows: DB
});
}); app.get('/add', function (req, res) {
res.render('add.html', {
rows: DB
});
}); app.post('/submit', function (req, res) {
const row = {}
row.name = req.body.name
row.age = req.body.age
DB.unshift(row);
res.redirect('/')
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
}) // 处理 404 响应
app.use(function (req, res, next) {
res.status(404).send("404")
})

3、package.json(即依赖包的变化):

{
...
"dependencies": {
"express": "^4.17.1",
"express-art-template": "^1.0.1"
}
}

运行的效果和用node写的一样,但编码更优雅。

path 模块

路径模块提供了用于处理文件和目录路径的实用程序。 可以使用以下命令访问它:

const path = require('path');

path.basename() 方法返回路径的最后一部分。尾部目录分隔符将被忽略。请看示例:

> path.basename('/foo/bar/baz/asdf/quux.html');
quux.html
> path.basename('/foo/bar/baz/asdf/quux.html', '.html');
quux
> path.basename('/foo/bar/baz/asdf/');
asdf

path.dirname() 方法返回路径的目录名称。尾部目录分隔符将被忽略。请看示例:

> path.dirname('/foo/bar/baz/asdf/quux');
/foo/bar/baz/asdf

path.extname(path) 返回扩展名

> path.extname('index.html');
.html
> path.extname('index.coffee.md');
.md
> path.extname('index.');
.
> path.extname('index');
''

path.parse() 方法返回一个对象,该对象的属性表示路径的重要元素。请看示例:

> path.parse('/home/user/dir/file.txt');
{
root: '/',
dir: '/home/user/dir',
base: 'file.txt',
ext: '.txt',
name: 'file'
}
> path.parse('C:\\path\\dir\\file.txt');
{
root: 'C:\\',
dir: 'C:\\path\\dir',
base: 'file.txt',
ext: '.txt',
name: 'file'
}

path.join() 方法使用特定于平台的分隔符作为分隔符,将所有给定的路径段连接在一起,然后对结果路径进行规范化。请看示例:

> path.join('/foo', 'bar', 'baz/asdf', 'quux');
\\foo\\bar\\baz\\asdf\\quux
> path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
\\foo\\bar\\baz\\asdf
> path.join('/foo', 'bar', 'baz/asdf', 'quux', './../..');
\\foo\\bar\\baz
> path.join('foo', {}, 'bar');
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of Object

path.isAbsolute() 是否是绝对路径。请看示例:

> path.isAbsolute('/foo/bar');
true
> path.isAbsolute('qux/');
false
> path.isAbsolute('.');
false

__dirname

如果你将 express 项目放在 demo 目录上一层运行 $ nodemon index,在通过浏览器访问 http://localhost:3000/,页面会出现报错信息:Error: Failed to lookup view "list.html" in views directory "D:\实验楼\node-study\views"

在文件里面用相对路径是不靠谱的。相对于运行 node 的目录,node 就是这么设计。

每个模块都有 __dirname,表示该文件的目录,是一个绝对路径,还有 __filename。请看示例:

Running node example.js from /Users/mjr

console.log(__filename);
// Prints: /Users/mjr/example.js
console.log(__dirname);
// Prints: /Users/mjr

我们可以通过 path.join(__dirname, 'xxx') 来修复上面的问题。将 index.js 改为下面的代码即可:

const path = require('path')
const express = require('express')
const app = express()
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded // view engine setup
app.engine('html', require('express-art-template'));
// 可以通过下面语句更改模板视图的文件夹,默认是 views
// app.set('views', path.join(__dirname, 'views'));
// 模拟数据库
const DB = [
{name: 'ph', age: '18'},
{name: 'lj', age: '19'}
];
const port = 3000
// 将静态资源对外开放
app.use('/public', express.static(path.join(__dirname, 'public'))) app.get('/', function (req, res) {
res.render(path.join(__dirname, 'views', 'list.html'), {
rows: DB
});
}); app.get('/add', function (req, res) {
res.render(path.join(__dirname, 'views', 'add.html'), {
rows: DB
});
}); app.post('/submit', function (req, res) {
const row = {}
row.name = req.body.name
row.age = req.body.age
DB.unshift(row);
res.redirect('/')
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
}) // 处理 404 响应
app.use(function (req, res, next) {
res.status(404).send("404")
})

其他章节请看:

前端学习 node 快速入门 系列

前端学习 node 快速入门 系列 —— 报名系统 - [express]的更多相关文章

  1. 前端学习 node 快速入门 系列 —— 初步认识 node

    其他章节请看: 前端学习 node 快速入门 系列 初步认识 node node 是什么 node(或者称node.js)是 javaScript(以下简称js) 运行时的一个环境.不是一门语言. 以 ...

  2. 前端学习 node 快速入门 系列 —— npm

    其他章节请看: 前端学习 node 快速入门 系列 npm npm 是什么 npm 是 node 的包管理器,绝大多数 javascript 相关的包都放在 npm 上. 所谓包,就是别人提供出来供他 ...

  3. 前端学习 node 快速入门 系列 —— 模块(module)

    其他章节请看: 前端学习 node 快速入门 系列 模块(module) 模块的导入 核心模块 在 初步认识 node 这篇文章中,我们在读文件的例子中用到了 require('fs'),在写最简单的 ...

  4. 前端学习 node 快速入门 系列 —— 简易版 Apache

    其他章节请看: 前端学习 node 快速入门 系列 简易版 Apache 我们用 node 来实现一个简易版的 Apache:提供静态资源访问的能力. 实现 直接上代码. - demo - stati ...

  5. 前端学习 node 快速入门 系列 —— 服务端渲染

    其他章节请看: 前端学习 node 快速入门 系列 服务端渲染 在简易版 Apache一文中,我们用 node 做了一个简单的服务器,能提供静态资源访问的能力. 对于真正的网站,页面中的数据应该来自服 ...

  6. MongoDB学习笔记:快速入门

    MongoDB学习笔记:快速入门   一.MongoDB 简介 MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统.在高负载的情况下,添加更多的节点,可以保证服务器性能.M ...

  7. 快速入门系列--WebAPI--03框架你值得拥有

    接下来进入的是俺在ASP.NET学习中最重要的WebAPI部分,在现在流行的互联网场景下,WebAPI可以和HTML5.单页应用程序SPA等技术和理念很好的结合在一起.所谓ASP.NET WebAPI ...

  8. 快速入门系列--MVC--07与HTML5移动开发的结合

    现在移动互联网的盛行,跨平台并兼容不同设备的HTML5越来越盛行,很多公司都在将自己过去的非HTML5网站应用渐进式的转化为HTML5应用,使得一套代码可以兼容不同的物理终端设备和浏览器,极大的提高了 ...

  9. vue 快速入门 系列 —— vue loader 上

    其他章节请看: vue 快速入门 系列 vue loader 上 通过前面"webpack 系列"的学习,我们知道如何用 webpack 实现一个不成熟的脚手架,比如提供开发环境和 ...

随机推荐

  1. React 17 All In One

    React 17 All In One v17.0.1 https://reactjs.org/blog/2020/10/20/react-v17.html https://reactjs.org/b ...

  2. Apple WWDC All In One

    Apple WWDC All In One https://developer.apple.com/wwdc20/ https://developer.apple.com/videos/wwdc202 ...

  3. Android vs iOS vs Web

    Android vs iOS vs Web UI view Android ViewGroup ImageView TextView iOS UIView ImageView TextView Web ...

  4. Open Collective

    Open Collective Open Collective is an online funding platform for open and transparent communities. ...

  5. JavaScript console.log Questions All In One

    JavaScript console.log Questions All In One "use strict"; /** * * @author xgqfrms * @licen ...

  6. BGV再度爆发,流通市值破500万美金!

    BGV似乎以超乎寻常的姿态,开启了爆发的模式.这两天,BGV一路上涨,日内最高涨至548.78美金,24小时成交额达到了98.07万美金,24小时成交量达到1844.93枚BGV,流通市值更是突破了5 ...

  7. 「NGK每日快讯」12.18日NGK公链第45期官方快讯!

  8. React 函数式组件的 Ref 和子组件访问(useImperativeHandle)

    引入:如何调用函数式组件内部的方法 对于 React 中需要强制修改子组件的情况,React 提供了 Refs 这种解决办法,使得我们可以操作底层 DOM 元素或者自定的 class 组件实例.除此之 ...

  9. [转]ROS 传感器消息及RVIZ可视化Laserscan和PointCloud

    https://blog.csdn.net/yangziluomu/article/details/79576508 https://answers.ros.org/question/60239/ho ...

  10. ISC BIND9 - 最详细、最认真的从零开始的BIND 9 服务讲解

    DNS and BIND 服务的搭建说明 目录 目录 DNS and BIND 服务的搭建说明 1. 背景 1.1 DNS 1.2 FQDN 1.3 BIND 1.4 本文中搭建模拟DNS服务网络虚拟 ...