Node.js:颠覆传统的服务器端开发
在传统的服务器端开发中,常见的选择是使用像Java、Python或Ruby这样的编程语言和对应的框架。这些语言虽然强大,但它们的模型往往是基于线程和同步I/O的,这意味着每个客户端请求都需要一个单独的线程来处理。当并发请求增加时,线程数量呈指数级增长,造成了巨大的资源消耗和性能问题。
这就是Node.js出现的背景。Node.js是基于Chrome V8引擎构建的JavaScript运行时,它采用了完全不同的开发模型。Node.js使用事件驱动和非阻塞I/O的方式处理请求,通过单线程和异步机制,实现高效的并发处理。这意味着在Node.js中,一个线程可以处理数千个并发连接,大大提高了服务器的性能和可伸缩性。
一、认识Web框架

二、Express安装

三、Express的基本使用

javascript
const express = require('express')
// 1.创建express的服务器
const app = express()
// 客户端访问URL: /login和/home
app.post('/login', (req, res) => {
// 处理login请求
res.end('登录成功, 欢迎回来~')
})
app.get('/home', (req, res) => {
res.end('首页的轮播图/推荐数据列表~')
})
// 2.启动服务器, 并且监听端口
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
四、认识中间件


4.1、注册普通中间件

javascript
const express = require('express')
const app = express()
// 总结: 当express接收到客户端发送的网络请求时, 在所有中间中开始进行匹配
// 当匹配到第一个符合要求的中间件时, 那么就会执行这个中间件
// 后续的中间件是否会执行呢? 取决于上一个中间件有没有执行next
// 通过use方法注册的中间件是最普通的/简单的中间件
// 通过use注册的中间件, 无论是什么请求方式都可以匹配上
// login/get
// login/post
// abc/patch
app.use((req, res, next) => {
console.log('normal middleware 01')
// res.end('返回结果了, 不要等了')
next()
})
app.use((req, res, next) => {
console.log('normal middleware 02')
})
// 开启服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
4.2、注册路径匹配的中间件
javascript
const express = require('express')
const app = express()
// 注册普通的中间件
// app.use((req, res, next) => {
// console.log('match normal middleware')
// res.end('--------')
// })
// 注册路径匹配的中间件
// 路径匹配的中间件是不会对请求方式(method)进行限制
app.use('/home', (req, res, next) => {
console.log('match /home middleware')
res.end('home data')
})
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
4.3、方法、路径匹配的中间件
javascript
const express = require('express')
const app = express()
// 注册中间件: 对path/method都有限制
// app.method(path, middleware)
app.get('/home', (req, res, next) => {
console.log('match /home get method middleware')
res.end('home data')
})
app.post('/users', (req, res, next) => {
console.log('match /users post method middleware')
res.end('create user success')
})
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
4.4、匹配多个中间件
javascript
const express = require('express')
const app = express()
// app.get(路径, 中间件1, 中间件2, 中间件3)
app.get('/home', (req, res, next) => {
console.log('match /home get middleware01')
next()
}, (req, res, next) => {
console.log('match /home get middleware02')
next()
}, (req, res, next) => {
console.log('match /home get middleware03')
next()
}, (req, res, next) => {
console.log('match /home get middleware04')
})
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
4.5、中间件案例一
先匹配普通中间件,有next()才会继续向下匹配
javascript
const express = require('express')
const app = express()
// 1.注册两个普通的中间件
app.use((req, res, next) => {
console.log('normal middleware01')
next()
})
app.use((req, res, next) => {
console.log('normal middleware02')
next()
})
// 2.注册路径path/method的中间件
app.get('/home', (req, res, next) => {
console.log('/home get middleware01')
next()
}, (req, res, next) => {
console.log('/home get middleware02')
next()
})
app.post('/login', (req, res, next) => {
console.log('/login post middleware')
next()
})
// 3.注册普通的中间件
app.use((req, res, next) => {
console.log('normal middleware03')
next()
})
app.use((req, res, next) => {
console.log('normal middleware04')
})
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
4.6、中间件案例二
javascript
const express = require('express')
const app = express()
// 注册两个实际请求的中间件
// 案例一: 用户登录的请求处理 /login post => username/password
app.post('/login', (req, res, next) => {
// 1.获取本次请求过程中传递过来的json数据
let isLogin = false
req.on('data', (data) => {
const dataString = data.toString()
const dataInfo = JSON.parse(dataString)
if (dataInfo.username === 'coderwhy' && dataInfo.password === '123456') {
isLogin = true
}
})
req.on('end', () => {
if (isLogin) {
res.end('登录成功, 欢迎回来~')
} else {
res.end('登录失败, 请检测账号和密码是否正确~')
}
})
})
// 案例二: 注册用户的请求处理 /register post => username/password
app.post('/register', (req, res, next) => {
// 1.获取本次请求过程中传递过来的json数据
let isRegister = false
req.on('data', (data) => {
const dataString = data.toString()
const dataInfo = JSON.parse(dataString)
// 查询数据库中该用户是否已经注册过
isRegister = false
})
req.on('end', () => {
if (isRegister) {
res.end('注册成功, 开始你的旅程~')
} else {
res.end('注册失败, 您输入的用户名被注册~')
}
})
})
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
4.7、中间件案例二(重构)
javascript
下面这个代码就相当于app.use(express.json())的原理,以后开发直接用app.use(express.json())
app.use((req, res, next) => {
if (req.headers['content-type'] === 'application/json') {
req.on('data', (data) => {
const jsonInfo = JSON.parse(data.toString())
req.body = jsonInfo
})
req.on('end', () => {
next()
})
} else {
next()
}
})
javascript
const express = require('express')
const app = express()
// app.use((req, res, next) => {
// if (req.headers['content-type'] === 'application/json') {
// req.on('data', (data) => {
// const jsonInfo = JSON.parse(data.toString())
// req.body = jsonInfo
// })
// req.on('end', () => {
// next()
// })
// } else {
// next()
// }
// })
// 直接使用express提供给我们的中间件
app.use(express.json())
// 注册两个实际请求的中间件
// 案例一: 用户登录的请求处理 /login post => username/password
app.post('/login', (req, res, next) => {
console.log(req.body)
})
// 案例二: 注册用户的请求处理 /register post => username/password
app.post('/register', (req, res, next) => {
console.log(req.body)
})
app.listen(9000, () => {
console.log('express服务器启动成功~')
})


4.8、中间件应用-urlencoded解析
javascript
const express = require('express')
// 创建app对象
const app = express()
// 应用一些中间件
app.use(express.json()) // 解析客户端传递过来的json
// 解析传递过来urlencoded的时候, 默认使用的node内置querystring模块
// { extended: true }: 不再使用内置的querystring, 而是使用qs第三方库
app.use(express.urlencoded({ extended: true })) // 解析客户端传递过来的urlencoded
// 编写中间件
app.post('/login', (req, res, next) => {
console.log(req.body)
res.end('登录成功, 欢迎回来~')
})
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
4.9、应用中间件 -- 第三方中间件

javascript
记录日志
const fs = require('fs')
const express = require('express')
const morgan = require('morgan')
// 创建app对象
const app = express()
// 应用第三方中间件
const writeStream = fs.createWriteStream('./logs/access.log')
app.use(morgan('combined', { stream: writeStream }))
// 编写中间件
app.post('/login', (req, res, next) => {
res.end('登录成功, 欢迎回来~')
})
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
单个文件上传
javascript
const express = require('express')
const multer = require('multer')
// 创建app对象
const app = express()
// 应用一个express编写第三方的中间件
const upload = multer({
dest: './uploads'
})
// 编写中间件
// 上传单文件: singer方法
app.post('/avatar', upload.single('avatar') , (req, res, next) => {
console.log(req.file)
res.end('文件上传成功~')
})
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
4.9、中间件应用-多个文件上传

javascript
const express = require('express')
const multer = require('multer')
// 创建app对象
const app = express()
// 应用一个express编写第三方的中间件
const upload = multer({
// dest: './uploads'
storage: multer.diskStorage({
destination(req, file, callback) {
callback(null, './uploads')
},
// 自定义文件名
filename(req, file, callback) {
callback(null, Date.now() + '_' + file.originalname)
}
})
})
// 编写中间件
// 上传单文件: single方法
app.post('/avatar', upload.single('avatar') , (req, res, next) => {
console.log(req.file)
res.end('文件上传成功~')
})
// 上传多文件:
app.post('/photos', upload.array('photos'), (req, res, next) => {
console.log(req.files)
res.end('上传多张照片成功~')
})
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
5.0、中间件应用-formdata解析

javascript
const express = require('express')
const multer = require('multer')
// 创建app对象
const app = express()
// express内置的插件
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
// 编写中间件
const formdata = multer()
app.post('/login', formdata.any(), (req, res, next) => {
console.log(req.body)
res.end('登录成功, 欢迎回来~')
})
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
5.1、客户端参数解析


javascript
const express = require('express')
// 创建app对象
const app = express()
// 编写中间件
// 1.解析queryString
app.get('/home/list', (req, res, next) => {
// offset/size
const queryInfo = req.query
console.log(queryInfo)
res.end('data list数据')
})
// 2.解析params参数
app.get('/users/:id', (req, res, next) => {
const id = req.params.id
res.end(`获取到${id}的数据~`)
})
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
5.2、服务器响应数据类型

javascript
const express = require('express')
// 创建app对象
const app = express()
// 编写中间件
app.post('/login', (req, res, next) => {
// 1.res.end方法(比较少)
// res.end('登录成功, 欢迎回来~')
// 2.res.json方法(最多)
// res.json({
// code: 200,
// message: '欢迎回来~',
// list: [
// { name: 'iPhone', price: 111 },
// { name: 'iPad', price: 111 },
// { name: 'iMac', price: 111 },
// { name: 'Mac', price: 111 },
// ]
// })
// 3.res.status方法: 设置http状态码
res.status(201)
res.json('创建用户成功~')
})
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
5.3、Express的路由

router/userRouter.js
javascript
const express = require('express')
// 1.创建路由对象
const userRouter = express.Router()
// 2.定义路由对象中的映射接口
userRouter.get('/', (req, res, next) => {
res.json('用户列表数据')
})
userRouter.get('/:id', (req, res, next) => {
const id = req.params.id
res.json('某一个用户的数据:' + id)
})
userRouter.post('/', (req, res, next) => {
res.json('创建用户成功')
})
userRouter.delete('/:id', (req, res, next) => {
const id = req.params.id
res.json('删除某一个用户的数据:' + id)
})
userRouter.patch('/:id', (req, res, next) => {
const id = req.params.id
res.json('修改某一个用户的数据:' + id)
})
// 3.将路由导出
module.exports = userRouter
javascript
const express = require('express')
const userRouter = require('./router/userRouter')
// 创建app对象
const app = express()
// 编写中间件
app.post('/login', (req, res, next) => {
})
app.get('/home', (req, res, next) => {
})
/** 用户的接口 */
// 1.将用户的接口直接定义在app中
// app.get('/users', (req, res, next) => {})
// app.get('/users/:id', (req, res, next) => {})
// app.post('/users', (req, res, next) => {})
// app.delete('/users/:id', (req, res, next) => {})
// app.patch('/users/:id', (req, res, next) => {})
// 2.将用户的接口定义在单独的路由对象中
// const userRouter = express.Router()
// userRouter.get('/', (req, res, next) => {
// res.json('用户列表数据')
// })
// userRouter.get('/:id', (req, res, next) => {
// const id = req.params.id
// res.json('某一个用户的数据:' + id)
// })
// userRouter.post('/', (req, res, next) => {
// res.json('创建用户成功')
// })
// userRouter.delete('/:id', (req, res, next) => {
// const id = req.params.id
// res.json('删除某一个用户的数据:' + id)
// })
// userRouter.patch('/:id', (req, res, next) => {
// const id = req.params.id
// res.json('修改某一个用户的数据:' + id)
// })
// 让路由生效
app.use('/users', userRouter)
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
5.4、express的静态资源服务器
javascript
const express = require('express')
// 创建app对象
const app = express()
// 内置的中间件: 直接将一个文件夹作为静态资源
app.use(express.static('./uploads'))
app.use(express.static('./build'))
// 编写中间件
app.post('/login', (req, res, next) => {
})
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})
5.5、express中错误处理的方案
javascript
const express = require('express')
// 创建app对象
const app = express()
app.use(express.json())
// 编写中间件
app.post('/login', (req, res, next) => {
// 1.获取登录传入的用户名和密码
const { username, password } = req.body
// 2.对用户名和密码进行判断
if (!username || !password) {
next(-1001)
} else if (username !== 'coderwhy' || password !== '123456') {
next(-1002)
} else {
res.json({
code: 0,
message: '登录成功, 欢迎回来~',
token: '323dfafadfa3222'
})
}
})
// 错误处理的中间件
app.use((errCode, req, res, next) => {
const code = errCode
let message = '未知的错误信息'
switch(code) {
case -1001:
message = '没有输入用户名和密码'
break
case -1002:
message = '输入用户名或密码错误'
break
}
res.json({ code, message })
})
// 启动服务器
app.listen(9000, () => {
console.log('express服务器启动成功~')
})