前端学习 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
- ...
-
路由
Tip: 主要介绍 express 重写报名系统需要用到的知识点,更多细节请参考 express 官网。
首页
Express - 基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
快速入门 - 安装
$ npm install express
快速入门 - hello-world
const express = require('express') const app = express() const ad8 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, ad0 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'] } }); });15b0
模板文件默认是 .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"。
在文件里面用相对路径是不靠谱的。相对于 15b0 运行 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 快速入门 系列 —— 简易版 Apache
- 前端学习 node 快速入门 系列 —— npm
- 前端学习 node 快速入门 系列 —— 模块(module)
- node.js/express.js新手快速入门及搭建MVC项目
- 开源喽,特别适合新手学习的一个系统。附有一个快速入门计划!《一句话发布系统》- One Word Delivery System
- Html5 学习系列(五)Canvas绘图API快速入门(2)
- Spring boot 入门系列(一):快速搭建一个简单web系统(简单的SSM框架)
- Castle学习系列(九)---Windsor框架快速入门
- 从零开始学习Node.js系列教程之基于connect和express框架的多页面实现数学运算示例
- Gradle学习系列之一——Gradle快速入门
- 应用广泛、入门简单的前端如何进行系统的学习,为未来的百万年薪做铺垫,简介目前招聘市场的基本需求
- Gradle学习系列之一——Gradle快速入门
- 前端系统学习快速进阶教程全攻略
- 从零学习node.js之express入门(六)
- SASS学习系列之四--------- 快速入门
- NodeJS简易博客系统(七)express框架入门学习
- Vue学习系列 -- vue-router 快速入门
- [新手入门]快速学习 ADO.NET Entity Framework系列文章 #1~#2
- Node.js入门系列——Express.js安装
- python学习系列一:python快速入门