Express 与 Koa 学习

一、Express

1.1 安装与简单使用

npm i express

// send 方法支持返回代码块、数据(自动转为JSON)、文本
// 注意:send 只能调用一次,后面再调也不会执行
const espress = require('express')

const app = espress()

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.get('/login', (req, res) => {
  res.send({
    code: 0,
    data: 'ok'
  })
})

app.listen(3000, () => {
  console.log('server start')
})

1.2 路由

1.2.1 字符串模式

// 匹配 acd 和 abcd
app.get('/ab?cd', (req, res) => {})

// 匹配 abe 和 abcde
app.get('/ab(cd)?e', (req, res) => {})

// 匹配 ab/***
app.get('/ab/:id', (req, res) => {})

// 匹配 abcd、abbcd、abbbcd等
app.get('/ab+cd', (req, res) => {})

// 匹配 abcd、abxcd、abiahiucd等
app.get('/ab*cd', (req, res) => {})

1.2.2 正则模式

// 匹配任何路径中含有 a 的路径
app.get('/a/', (req, res) => {})

// 匹配以 fly 结尾的路径
app.get('/.*fly$/', (req, res) => {})

1.2.3 路由守卫

第一种写法

const espress = require('express')

const app = espress()

// 路由守卫
app.get('/', (req, res, next) => {
  // 登录鉴权,验证 token
  const isLogin = Math.random() > 0.5
  if (isLogin) {
    next()
  } else {
    res.send('401 error')
  }
}, (req, res) => {
  res.send('欢迎来到 Home 页面')
})

app.listen(3000, () => {
  console.log('server start')
})

第二种写法

const espress = require('express')

const app = espress()

function func1(req, res, next) {
  // 登录鉴权,验证 token
  const isLogin = Math.random() > 0.5
  if (isLogin) {
    next()
  } else {
    res.send('401 error')
  }
}

function func2(req, res) {
  res.send('欢迎来到 Home 页面')
}

app.get('/', [func1, func2])

app.listen(3000, () => {
  console.log('server start')
})

1.3 中间件

1.3.1 应用级中间件

应用级中间件绑定到 app 对象

const espress = require('express')

const app = espress()

// 访问任何路径都会先走 func1 的逻辑
app.use(func1)

function func1(req, res, next) {
  // 登录鉴权,验证 token
  const isLogin = Math.random() > 0.5
  if (isLogin) {
    next()
  } else {
    res.send('401 error')
  }
}

app.get('/home', (req, res) => {})
app.get('/login', (req, res) => {})

app.listen(3000, () => {})

1.3.2 路由级中间件

跟应用级中间件一样,只是绑定的对象为 express.Router()

// index.js
const espress = require('express')

const app = espress()
const HomeRouter = require('./HomeRouter')

// 以 /home 开头的路径匹配
app.use('/home', HomeRouter)
// HomeRouter.js
const espress = require('express')

const router = espress.Router()

// 访问 /home 走这里逻辑
router.get('/', (req, res) => {
  console.log('/home')
  res.send('home')
})

module.exports = router 

1.3.3 错误中间件

// 放在路由注册最后,所有路径匹配不到时
const espress = require('express')

const app = espress()

app.get('/login', (req, res) => {
  res.send({
    code: 0,
    data: 'ok'
  })
})

// 上面的路径都匹配不到走这里
app.use((req, res) => {
  // 抛出 404
  res.status(404).send('404 Not Found')
})

app.listen(3000, () => {
  console.log('server start')
})

1.3.4 内置中间件

express.static 是 Express 唯一内置的中间件。基于 serve-static,负责在 Express 应用中提托管静态资源,每个应用可由多个静态目录

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

1.3.5 第三方中间件

npm i cookie-parser
const CookieParser = require('cookie-parser')

app.use(CookieParser)

1.4 获取请求参数

1.4.1 get 请求

const express = require('express')

const app = express()

// 获取get请求参数
app.get('/', (req, res) => {
  // http://localhost:3000/?username=joe
  console.log(req.query) // { username: 'joe' }
  res.send({
    code: 0,
    data: 'get success'
  })
})

app.listen(3000, () => {
  console.log('server start')
})

1.4.2 post 请求

const express = require('express')

const app = express()

// 使用内置中间件解析post参数 --- Content-Type: application/x-www-form-urlencoded 格式
app.use(express.urlencoded({ extended: true }))

// 使用内置中间件解析post参数 --- Content-Type: application/json 格式
app.use(express.json())

// 获取post请求参数
app.post('/login', (req, res) => {
  console.log(req.body) // { username: 'joe', password: '123' }
  res.send({
    code: 0,
    data: 'post success'
  })
})

app.listen(3000, () => {
  console.log('server start')
})

Content-Type: application/x-www-form-urlencoded 格式
在这里插入图片描述
Content-Type: application/json 格式
在这里插入图片描述

1.5 静态资源

// 将 public 文件夹当作静态资源目录,访问的时候可以省略 /public 
app.use(express.static('public'))

1.6 ejs 模板

npm i ejs

  • <% %>,流程控制标签(写的是 if else,for)
  • <%= %>,输出标签(原文输出HTML标签)
  • <%- %>,输出标签(HTML标签会被浏览器解析)
  • <%# %>,注释标签
  • <%- include('header', { isShow: false }) %>,导入公共模板

需要在应用中进行如下设置才能让 Express 渲染模板文件:

  1. views,放模板文件的目录,比如: app.set('views', './views')
  2. view engine,模板引擎,比如:app.set('view engine', 'ejs')

1.6.1 简单使用

// index.js
const express = require('express')

const app = express()
app.set('views', './views')
app.set('view engine', 'ejs')


app.get('/', (req, res) => {
  // 这里会找 ./views 下的 login.ejs 渲染
  res.render('login', { error: '' })
})

app.listen(3000, () => {
  console.log('server start')
})
// views/login.ejs
<div>login 页面, <%= error %></div>

1.6.2 for 循环遍历数据

// index.js
app.get('/home', (req, res) => {
  res.render('home', { list: [1, 2, 3] })
})
// views/home.ejs
<ul>
  <% for(let i = 0; i < list.length; i++) { %>
    <li><%= list[i] %> </li>
  <% } %> 
</ul>

1.7 文件上传

说明文档
Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件

npm i multer

const express = require('express')
const multer  = require('multer')
// 文件存放在 uploads 文件夹下
const upload = multer({ dest: 'uploads/' })

const app = express()

app.post('/profile', upload.single('avatar'), function (req, res, next) {
  // req.file 是 `avatar` 文件的信息(前端传文件的字段)
  const filename = `/uploads/${req.file.filename}` // 获取文件之后存数据库
  // req.body 将具有文本域数据,如果存在的话
})

app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
  // req.files 是 `photos` 文件数组的信息
  // req.body 将具有文本域数据,如果存在的话
})

1.8 应用程序生成器

通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。

npx express-generator
npm install -g express-generator
express

创建 ejs 模板项目

express --view=ejs myapp
cd myapp
npm install
npm start

1.9 Cookie 与 Session

1)下载 express-session
npm i express-session

2)引入 express-session

// 入口文件 app.js
var express = require('express')
var expressSession = require('express-session')
var MongoStore = require('connect-mongo')

var indexRouter = require('./routes/index')

var app = express()

// 注册 session 中间件,放在注册路由前面
app.use(expressSession({
  name: 'user', // 下发给客户端的 cookie 名
  secret: 'session', // 服务器生成的 session 签名,也就是密钥
  resave: true, // 设置为 true 表示重新设置 session 对象后会重新计算过期时间
  saveUninitialized: true, //  设置为 true 表示刚开始就会下发一个 cookie,但是需要与服务器通信后才有效
  cookie: {
    maxAge: 1000 * 60 * 60, // cookie 过期时间,这里为一小时
    secure: false, // 为 true 时只有 https 协议才能通过 document.cookie 访问
  },
  // rolling: true, // 为 true 时表示超时前刷新,cookie 会重新计时;false 表示超时前刷新多少次,都按第一次刷新开始计时
  store: MongoStore.create({
    mongoUrl: 'mongodb://127.0.0.1:27017/express_session', // 新建一个数据库存放 session
    ttl: 1000 * 60 * 60, // cookie 过期时间,跟 maxAge 一致
  })
}))

app.use('/', indexRouter)

3)登录成功设置 session

// 登录接口,req.session 是自动匹配 cookie 生成的对象,不同浏览器生成 session 的对象不同
router.post('/login', function(req, res) {
  const { name, password } = req.body
  // 成功,req.session 添加一个字段记录已登录 例如添加 user 字段
  req.session.user = true
})

4)设置中间件,session 过期校验

app.use((req, res, next) => {
  // login 相关路由
  if (req.url.includes('/login')) {
    next()
    return
  }

  // 校验是否有 cookie
  if (req.session.user) {
    // 重新设置 session 对象,刷新过期时间
    req.session.user = Date.now()
    next()
  } else {
    // 如果是接口就返回错误码,如果是路由就重定向 login 页面
    req.url.includes('/add') ? res.status(401).send({ ok: -1 }) : res.redirect('/login')
  }
})

1.10 Json Web Token(JWT)

npm i jsonwebtoken

const jwt = require('jsonwebtoken')

// 加密。第一个参数是加密数据 第二个参数是密钥 第三个参数设置过期时间
const token = jwt.sign({
  name: 'admin'
  // expiresIn 支持 1h 1d 写法
}, 'anydata', { expiresIn: '10s' })

// 解密。第一个参数是加密数据 第二个参数是密钥(需跟加密密钥一致)
const decoded = jwt.verify(token, 'anydata')

二、Koa

2.1 简单使用

npm i koa

const Koa = require('koa')

const app = new Koa()

// ctx 上下文
/*
  ctx.req - Node 的 request 对象
  ctx.res - Node 的 response 对象
  ctx.request - koa 的 request 对象
  ctx.response - koa 的 response 对象
*/
app.use((ctx, next) => {
  // response.body 是往外输出的
  // ctx.body - ctx.response.body 简写方式
  ctx.response.body = 'hello world'
})

app.listen(3000)

2.2 Koa 对比 Express

1)更轻量

  • koa 不提供内置的中间件
  • koa 不提供路由,而是把路由这个库分离出来了(koa/router)

2)Context 对象
koa 增加了一个 context 对象作为这次请求的上下文对象(在 koa2 中作为中间件的第一个参数传入)。同时 Context 上也挂载了 request 和 response 两个对象

3)异步流程控制
express 使用 callback 来处理异步,koa v1 使用 generator,koa v2 使用 async/await

2.3 Koa 路由

npm i koa-router

2.3.1 注册路由

const Koa = require('koa')
const KoaRouter = require('koa-router')

const app = new Koa()
const router = new KoaRouter()

router.get('/', (ctx, next) => {
  ctx.body = 'hello world!'
})

// allowedMethods 的作用是当请求方式出错时,响应头 Allow 会返回正确的请求方式
app.use(router.routes()).use(router.allowedMethods())

app.listen(3000)

2.3.2 分模块注册路由

1)入口文件

// 入口文件 app.js
const Koa = require('koa')

const router = require('./router/index')

const app = new Koa()

app.use(router.routes()).use(router.allowedMethods())

app.listen(3000)

2)路由入口文件

// router/index.js
const KoaRouter = require('koa-router')

const userRouter = require('./user')
const listRouter = require('./list')

const router = new KoaRouter()

// 统一加前缀
// router.prefix('/api')

// 注册路由级组件
router.use('/user', userRouter.routes(), userRouter.allowedMethods())
router.use('/list', listRouter.routes(), listRouter.allowedMethods())

// 重定向
router.redirect('/', '/user')

module.exports = router

3)路由模块文件

// router/user.js
const Koa = require('koa')
const KoaRouter = require('koa-router')

const app = new Koa()
const router = new KoaRouter()

router.get('/', (ctx, next) => {
  ctx.body = 'user page'
})

app.use(router.routes()).use(router.allowedMethods())

module.exports = router

2.4 静态资源

npm i koa-static

const static = require('koa-static')

// 将 public 文件夹设为静态资源目录
app.use(static(
  path.join(__dirname, 'public')
))

2.5 获取请求参数

2.5.1 get 请求

// http://localhost:3000/list?page=0&size=10
router.get('/', (ctx, next) => {
  console.log(ctx.query) // { page: '0', size: '10' }
  ctx.body = 'list page'
})

// http://localhost:3000/list/123
router.get('/:id', (ctx, next) => {
  console.log(ctx.params) // { id: '123' }
  ctx.body = 'list page'
})

2.5.2 post 请求

const Koa = require('koa')
const KoaRouter = require('koa-router')
const KoaBodyparser = require('koa-bodyparser')

const app = new Koa()
const router = new KoaRouter()

app.use(KoaBodyparser())

router.post('/list', (ctx, next) => {
  console.log(ctx.request.body)
  ctx.body = 'list'
})
app.use(router.routes()).use(router.allowedMethods())

app.listen(3000)

2.6 ejs 模板

npm i ejs koa-views

// 入口文件
const Koa = require('koa')
const path = require('path')
const KoaRouter = require('koa-router')
const KoaViews = require('koa-views')

const app = new Koa()
const router = new KoaRouter()

app.use(KoaBodyparser())
// 配置模板引擎
app.use(KoaViews(path.join(__dirname, 'views'), { extension: 'ejs' }))

// 异步读取文件,必须使用 async await
router.get('/', async (ctx, next) => {
  // 渲染 views 文件夹下的 index.ejs
  await ctx.render('index')
})

app.use(router.routes()).use(router.allowedMethods())

app.listen(3000)

2.7 cookie 与 session

2.7.1 cookie

koa 提供了从上下文直接读取、写入 cookie 的方法

  • ctx.cookies.get(name, [options]) 读取上下文请求中的 cookie
  • ctx.cookies.set(name, value, [options]) 在上下文中写入 cookie

2.7.2 session

1)下载 koa-session-minimal
npm i koa-session-minimal

2)引入 koa-session-minimal

const session = require('koa-session-minimal')

app.use(session({
  key: 'sessionId',
  // cookie 属性配置
  cookie: {
    maxAge: 1000 * 60
  }
}))

3)登录成功设置 session

// 登录接口,ctx.session 是自动匹配 cookie 生成的对象,不同浏览器生成 session 的对象不同
router.post('/login', function(ctx, next) {
  const { name, password } = ctx.request.body
  // 成功,ctx.session 添加一个字段记录已登录 例如添加 user 字段
  ctx.session.user = true
})

4)设置路由中间件,session 过期校验

app.use(async (ctx, next) => {
  // login 相关路由
  if (ctx.url.includes('/login')) {
    await next()
    return
  }

  // 校验是否有 cookie
  if (ctx.session.user) {
    // 重新设置 session 对象,刷新过期时间
    ctx.session.user = Date.now()
    await next()
  } else {
    // 重定向 login 页面
    ctx.redirect('/login')
  }
})

2.8 文件上传

说明文档
npm i @koa/multer multer

const Koa = require('koa')
const KoaRouter = require('koa-router')
const multer  = require('@koa/multer')
// 文件存放在 uploads 文件夹下
const upload = multer({ dest: 'uploads/' })

const app = new Koa()
const router = new KoaRouter()

router.post('/profile', upload.single('avatar'), function (ctx, next) {
  // ctx.file 是 `avatar` 文件的信息(前端传文件的字段)
  const filename = `/uploads/${ctx.file.filename}` // 获取文件之后存数据库
})

router.post('/photos/upload', upload.array('photos', 12), function (ctx, next) {
  // ctx.files 是 `photos` 文件数组的信息
})
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值