前端学习 node 快速入门 系列 —— 报名系统 - [express]
其他章节请看:
报名系统 - [express]
最简单的报名系统:
- 只有两个页面
- 人员信息列表页:展示已报名的人员信息列表。里面有一个
报名
按钮,点击按钮则会跳转到报名页 - 报名页:用于报名。里面是一个表单,可以输入姓名和年龄,点击
保存
,成功后会跳转到人员信息列表页
本文主要分 3 部分:
- 使用 node 实现这个项目
- 介绍 express 相关知识
- 使用 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")
})
其他章节请看:
出处:https://www.cnblogs.com/pengjiali/p/14614727.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。