【0-1搭建网站】(一)Koa+koa/router+module-alias+eslint+prettier

881 阅读5分钟

前言

  • 很久、很久、很久没有更新技术文章了!
  • 突然想起自己本来就是做技术的,那就利用自己会的东西,给我女儿留一些有意义的东西。
  • 于是打算搭建一个网站,用来记录她的成长历程。

后端基础搭建

环境准备

// 系统
Windows 10
// 编辑器
VS Code
// 接口测试
Postman
// node
v21.7.1
// npm
10.5.0
// Terminal
Git Bash
  • 之前都是使用的 Mac,所以终端使用的是 Git Bash,你也可以使用 Windows 自带的或者按自己习惯安装其他的。

项目初始化

mkdir koa-app
cd koa-app
npm init -y
  • 安装 Koa
// 2.15.2
npm i Koa
  • 创建最简单的 Koa 项目,创建 index.js 文件,作为项目的入口文件。
// index.js
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx) => {
  ctx.type = 'text/plain'
  ctx.body = 'Hello Koa!'
})
const port = process.env.PORT || 3000
app.listen(port, () => {
  console.log(`Server is running on port ${port}`)
})
  • 在项目根目录执行 node ./index.js 启动项目,可在浏览器打开 http://localhost:3000/ 查看运行结果,或使用 PostmanGET 请求访问 http://localhost:3000/image.png image.png

添加路由

  • 安装路由
// 12.0.1
npm i @koa/router
  1. 修改 index.js 文件
// index.js
const Koa = require('koa')
+ const Router = require('@koa/router')
+ const router = new Router()
- app.use(async (ctx) => {
-   ctx.type = 'text/plain'
-   ctx.body = 'Hello Koa!'
- })
+ router.get('/hello', async (ctx) => {
+  ctx.body = 'Hello Koa!!!'
+ })
+ app.use(router.routes()).use(router.allowedMethods())
...
  1. 重新启动项目,可在浏览器打开 http://localhost:3000/hello 查看运行结果,或使用 PostmanGET 请求访问 http://localhost:3000/hello

添加热更新

  • 每次修改文件后都需要停止并重启服务,比较麻烦。接入 nodemon 可以监听文件变化自动重启服务。
  • 注:nodemon 是全局安装,不需要安装在项目中。
// 3.1.0
npm i -g nodemon
  1. 修改 package.json 文件中 scripts 如下:
//  package.json
{
  ...,
  "scripts": {
+     "start": "nodemon ./index.js",
-     "test": "echo \"Error: no test specified\" && exit 1"
  },
  ...
}
  1. 使用 npm startyarn start 启动项目即可。
  • 没有安装 yarn 的,执行 npm i -g yarn 全局安装即可。

添加 ESlintPrettier

  • 为了后续开发方便、增强代码可读性,先把代码风格定好。
  • 首先在 VS Code 中把相应的扩展装上。 image.png
  • 在项目中安装相关的依赖
// 8.57.0、3.2.5、9.1.0、5.1.3
npm i eslint prettier eslint-config-prettier eslint-plugin-prettier --save-dev
  1. 在根目录添加 .eslintrc.js 文件并添加以下配置:
// .eslintrc.js
module.exports = {
  env: {
    node: true,
    es2021: true,
  },
  extends: ['eslint:recommended', 'plugin:prettier/recommended'],
  parserOptions: {
    ecmaVersion: 12,
  },
  rules: {
    'prettier/prettier': ['error', { endOfLine: 'auto' }],
  },
}
  1. 在根目录添加 .prettierrc 文件并添加以下配置
// .prettierrc
{
  "singleQuote": true,
  "semi": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "proseWrap": "never"
}
  • VS Code 中可开启保存自动格式化,也可每次自己手动格式化,看个人习惯。

支持接收 POST 数据

  • PostmanHeaders 配置中添加 Content-Type application/json,请求方式为 POST
  • 在项目需要安装依赖
// 4.4.1
npm i koa-bodyparser
  • 修改 index.js 文件
// index.js
...
+ const bodyParser = require('koa-bodyparser')
const app = new Koa()
+ app.use(bodyParser())
+ router.post('/login', async (ctx) => {
+   const { username, password } = ctx.request.body
+   ctx.body = { username, password }
+ })
  • 使用 PostmanPOST 请求访问 http://localhost:3000/loginimage.png

调整目录结构

  1. 在根目录新建 src 文件夹,移动 index.jssrc 目录;
  2. src 目录下新建 routes 文件,用于存放不同模块的接口文件;
  3. routes 目录下新建 user.js 文件,用于存放用户相关的接口。
  • 调整后目录结构如下:
|- node_modules
|- src
  |- routes
    |- user.js
  |- index.js
|- .eslint.js
|- .prettierrc
|- package-lock.json
|- package.json
  • 注:由于调整了入口文件的位置,所以 package.json 中的启动命令需要修改。
//  package.json
{
  ...,
  "scripts": {
-     "start": "nodemon ./index.js",
+     "start": "nodemon ./src/index.js",
  },
  ...
}
  • 拆分原来的 index.js 文件
  • 拆分后的 src/index.js 文件,如下:
// src/index.js
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const userRouter = require('./routes/user')
const app = new Koa()
app.use(bodyParser())
app.use(userRouter.routes()).use(userRouter.allowedMethods())

const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`)
})
  • 新建的 src/routes/user.js,如下:
// src/routes/user.js
const Router = require('@koa/router')
const router = new Router()
router.get('/hello', async (ctx) => {
  ctx.body = 'Hello Koa!!!'
})
router.post('/login', async (ctx) => {
  const { username, password } = ctx.request.body
  ctx.body = { username, password }
})
module.exports = router

配置路径别名

  • 在项目中我们会拆分不同的文件,拆分后就涉及到文件导入,如果没有路径别名的话,我们可能会写很多像下面这样的导入语句:
// 示例
const test = require('../../../test')
const mock = require('../../../../mock')
...
  • 像上面这样的导入语句我们在看的时候就很难知道原始文件在哪个目录下,可读性非常的差。于是我们可以添加路径别名相关的配置。
  • Koa 中我们可以安装 module-alias 来配置路径别名
// 2.2.3
npm i module-alias --save-dev
  1. 修改 package.json 文件
// package.json
{
  ...,
  "_moduleAliases": {
    "@src": "src",
    "@constants": "src/constants",
    "@routes": "src/routes",
    "@utils": "src/utils"
  }
}
  1. 修改 src/index.js 文件,文件顶部添加 require('module-alias/register')
// src/index.js
+ require('module-alias/register')
...
  1. 配置好后,我们就可以使用刚才声明好的路径别名来导入对应路径下的文件了。
// 示例
// old
const test = require('../../../test')
const mock = require('../../../../mock')
// new
const test = require('@xxx/test')
const mock = require('@xxx/mock')

目录结构延伸

  • 在配置别名时,可以看到我在 routes 同级又添加了两个目录
    1. constants:用于存放项目中多处(2处及以上)使用的一些常量;
    2. utils:用于存放一些项目中使用的通用函数(日期处理、数据处理等)。

使用环境变量

  • 由于考虑一些变量在项目中使用,但又不想导入(例如:服务启动的 PORT、全局路由的 prefixtoken 加密的 secret_key等),所以我引入了 dotenv
// 16.4.5
npm i dotenv
  1. 在项目根路径新建 .env 文件,并添加以下代码:
// .env
PORT = 3000
SEREAT_KEY = ''
  1. 修改 src/index.js 文件,文件顶部添加 require('dotenv').config()
// src/index.js
+ require('dotenv').config()
...
  • 在项目中使用 process.env[key] 即可拿到对应的值,不需要引入文件。

添加登录及登录验证

  • 既然是网站那一般就会涉及到登录,网站的后台管理端、网站管理员登录。先使用 mock 的用户数据实现一个简易的登录流程。
  • 我用的是 jsonwebtoken 来做登录及登录验证相关的操作,安装 jsonwebtoken
// 9.0.2
npm i jsonwebtoken
  1. 封装登录及登录验证相关的方法:
// utils/tokenOpt.js
const jwt = require('jsonwebtoken')
// '10s': 十秒
// '30m': 三十分钟
// '1d': 一天
// '2h': 两小时
// '1w': 一周(与 '7d' 等效)
// '2d 6h': 两天六小时
// '1w 3d': 一周三天
const createToken = (user) => {
  return jwt.sign(
    { id: user.id, username: user.username },
    process.env.SEREAT_KEY,
    { expiresIn: '1d' },
  )
}

const verifyToken = (ctx, token, resolve, reject) => {
  jwt.verify(token, process.env.SEREAT_KEY, (err, decoded) => {
    if (err) {
      reject(err)
    } else {
      resolve(decoded)
    }
  })
}
module.exports = { createToken, verifyToken }
  1. 修改 src/routes/user.js 文件为用户相关接口
// src/routes/user.js
const Router = require('koa-router')
const router = new Router({
  prefix: '/user', // 设置前置路由
})
router.post('/info', async (ctx) => {
  const { username, password, query } = ctx.request.body
  ctx.body = { username, password, query }
})
module.exports = router
  1. 新建 src/routes/login.js 文件用于登录
// src/routes/login.js
const Router = require('koa-router')
const { createToken } = require('@utils/tokenOpt')
const router = new Router()
const users = [ // 模拟的用户数据
  { id: 1, username: 'user1', password: 'password1' },
  { id: 2, username: 'user2', password: 'password2' },
]
router.post('/login', async (ctx) => {
  const { username, password } = ctx.request.body
  const user = users.find(
    (user) => user.username === username && user.password === password,
  )
  if (user) {
    const token = createToken(user)
    ctx.body = { token }
  } else {
    ctx.status = 401
    ctx.body = { error: 'Invalid username or password' }
  }
})
module.exports = router
  1. 修改 src/index.js 文件,引入新增的 routes 文件,注册路由。注:Koa 执行是洋葱模型,所以这里我们要注意注册路由的顺序。
// src/index.js
const userRouter = require('@routes/user')
+ const loginRouter = require('@routes/login')
app.use(bodyParser())
+ app.use(loginRouter.routes(), loginRouter.allowedMethods())
app.use(userRouter.routes(), userRouter.allowedMethods())
  1. 到这儿,只是用到生成 token,其他接口并没有做 token 验证,所以我们在登录后的接口还需要加一个 token 验证拦截。当然,如果有不需要验证的接口,你可以放到验证中间件之前,或者写到 token 验证中间的白名单中。添加 src/utils/authenticate.js 用户验证 token
// src/utils/authenticate.js
const { verifyToken } = require('@utils/tokenOpt')
const authenticate = async (ctx, next) => {
  const { authorization } = ctx.headers
  if (!authorization) {
    ctx.throw(401, 'Authentication token is required')
  }
  if (authorization && authorization.startsWith('Bearer ')) {
    try {
      const token = authorization.substring(7)
      ctx.token = token
      const decodedInfo = await new Promise((resolve, reject) => {
        verifyToken(ctx, token, resolve, reject)
      })
      ctx.userInfo = decodedInfo
    } catch (error) {
      ctx.throw(401, 'Invalid token')
    }
    await next()
  } else {
    ctx.throw(401, 'Invalid token')
  }
}
module.exports = authenticate
  1. src/index.js 文件中使用 token 验证中间件
app.use(loginRouter.routes(), loginRouter.allowedMethods())
+ app.use(authenticate)
app.use(userRouter.routes(), userRouter.allowedMethods())
  1. 运行项目试试
  • 登录获取 token image.png
  • 不携带 token 获取用户信息 image.png
  • 使用无效 token 获取用户信息 image.png
  • 使用有效 token 获取用户信息 image.png

总结

  1. 至此,我们后端框架的基础配置已经完成了,回头看看我们主要做了些什么
    1. 初始化 Koa 项目框架
    2. 支持热更新启动
    3. 代码支持 ESlintPrettier 美化
    4. 文件导入支持别名配置
    5. 支持多种请求方式
    6. 可使用环境变量
    7. 登录、登录验证
  2. 至此当前的目录结构:
|- node_modules
|- src
  |- constants
  |- routes
    |- user.js
  |- index.js
  |- utils
    |- authenticate.js
    |- createOpt.js
|- .env
|- .eslint.js
|- .prettierrc
|- package-lock.json
|- package.json

后续计划

  • 支持 GraphQL
  • 环境区分
  • 全局路由 prefix
  • 返回 JSON 数据
  • 数据库
  • ...

最后

  • 后端框架的源码我将在所有需要用到的配置都搭建好之后,放在我的 github 上。如果需要这个版本的,也可以私信我,私信可能回复不及时,还请理解。
  • 有什么问题或想要在项目中增加什么配置欢迎大家评论区留言交流。

往期精彩

「点赞、收藏和评论」

❤️关注+点赞收藏+评论+分享❤️,手留余香,谢谢🙏大家。