持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情
在如何理解express框架文章中,我们介绍了express的基本使用,本片文章将利用typescript和express开发一个服务端的demo。
项目目录
package.json
相关配置参考ts学习心得: nodejs项目配置package.json
"scripts": {
"dev:build": "tsc -w",
"dev:start": "nodemon ./build/index.js",
"dev": "tsc && concurrently npm:dev:*",
"mock": "json-server --watch ./data/course.json --port 3004"
}
- 利用
tsc -w把ts文件编译成js文件,使其能在nodejs中运行(ts是不能运行的) - 利用
nodemon来监控并执行./build/index.js文件 - 利用
concurrently同时启动这两个命令 - 利用
&&先后执行命令 - 利用
json-server来启动一个静态资源服务器,读取public/index.html文件
src/index.ts
import express from 'express'
const app = express()
app.get('/', (req, res) => {
res.end('hello world')
})
app.listen('7001', () => {
console.log('listen at 7001')
})
利用ts+express开发遇到的问题
问题一
express 库的类型定义文件 .d.ts 文件类型描述不准确。
router.post('/getData', (req: Request, res: Response) => {
const { password, username } = req.body
if (req.body.password === '123') {}
}
我们可以看到password是 any 类型,这是因为 @types/express对body设置的是any类型
export interface Request< ... ReqBody = any ... > {
body: ReqBody;
}
这就不符合我们的预期了,它应该是一个字符串类型,或者是undefined(前端有可能没有传值),所以需要对类型文件进行修改,但是我们又不能在@types/express上做修改,怎么办呢?
在route.ts代码中加上RequestWithBody接口,让这个接口继承Request,并添加body属性,body里面是一个可索引的类型。这样password就是字符串或者undefined类型了。
interface RequestWithBody extends Request {
body: {
[key: string]: string | undefined
}
}
router.post('/getData', (req: RequestWithBody, res: Response) => {
const { password, username } = req.body
if (req.body.password === '123') {}
}
问题二
当我们使用中间件修改req,但是req的类型文件没有跟着变化。
app.use((req: Request, res: Response, next: NextFunction) => {
req.teacherName = 'dell'
next()
})
我们可以定义一个custom.d.ts文件,在Request的接口中添加我们加入的属性,这样在使用的时候就会出现提示。custom.d.ts文件会和@types/express里面的类型文件进行合并。
declare namespace Express {
interface Request {
teacherName: string
}
}
登录登出接口
解析前端传过来的数据
在index.ts加入解析请求头的中间件,我们可以安装body-parser,也可以始终express自带的函数来解析。
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
给请求头加上额外的类型
interface RequestWithBody extends Request {
body: {
[key: string]: string | undefined
}
}
封装返回的数据格式
interface Result {
success: boolean
errMsg?: string
data: any
}
export const getResponseResult = (data: any, errMsg?: string): Result => {
if (errMsg) {
return {
success: false,
errMsg,
data
}
}
return {
success: true,
data
}
}
cookie-session
用户的session有两种存储方式,一种是存放在客户端的cookie里面,另一种是存放在服务端的数据库里面(redis)。cookie-session采用的是第一种方式,即把用户信息加密后通过set-cookie存放在客户端的cookie里面,这种方法的优势是没必要在服务端搞个数据库,非常麻烦。
import cookieSession from 'cookie-session'
app.use(
cookieSession({
name: 'session',
keys: ['xiaopen'],
// Cookie Options
maxAge: 24 * 60 * 60 * 1000 // 24 hours
})
)
新建router.ts
import { Router, Request, Response, NextFunction } from 'express'
const router = Router()
router.get('/logout', (req, res, next) => {
if (req.session) {
req.session.isLogin = undefined
}
res.json(getResponseResult(true))
})
router.post(
'/login',
(req: RequestWithBody, res: Response, next: NextFunction) => {
const { password } = req.body
const isLogin = req.session?.isLogin
if (isLogin) {
res.end('already login')
} else {
if (password === '123' && req.session) {
req.session.isLogin = true
req.session.username = 'pengchangjun'
req.session.userId = '1234567890'
res.json(getResponseResult(true))
} else {
res.json(getResponseResult(null, 'login error'))
}
}
}
)
export default router
当登录后,我们会在req.session写入一些用户信息(isLogin, username, userId),同时在响应头加入set-cookie把这些信息写入到浏览器cookie中。
当用户下次访问的时候会带上cookie,然后cookie-session就会解密cookie,把解密后的信息挂载到req.session,这样我就可以通过req.session来判断这个用户是否登录及用户信息了。
权限验证中间件
大部分的接口都需要验证用户是否登录:
router.get('/showData', (req, res) => {
const isLogin = req.session?.isLogin
if (isLogin) {
const path1 = path.resolve(__dirname, '../data/course.json')
const content = fs.readFileSync(path1, 'utf-8')
res.json(JSON.parse(content))
} else {
res.end(`please login`)
}
})
但是这样写非常麻烦,我们可以把权限验证的功能写成一个中间件:
const checkLogin = (req: Request, res: Response, next: NextFunction) => {
const isLogin = req.session?.isLogin
if (isLogin) {
next()
} else {
res.json(getResponseResult(null, 'please login'))
}
}
router.get('/showData', checkLogin, (req, res) => {
const path1 = path.resolve(__dirname, '../data/course.json')
const content = fs.readFileSync(path1, 'utf-8')
res.json(JSON.parse(content))
})
总结
综合上面的代码,我们发现ts仅仅只是提供了类型检查的功能,感觉用处也不是很大,这是因为上面的代码没有把ts最强的优势运用进来。上面写的代码是一个过程化的代码,即面向过程编程,要想最大限度的利用ts,就必须要把面向对象类的概念应用进来。下一篇文章让我们见识下typescript的真实威力。