Vue3+Node.js

Nodejs

Node.js 是一个基于 Chrome V8 引擎构建的开源、跨平台的 JavaScript 运行时环境。它让开发者能够使用 JavaScript 编写服务器端代码,将 JavaScript 的应用范围从浏览器扩展到了服务器、本地脚本等领域。

🛠️ 典型应用场景

基于以上特点,Node.js 在不同场景下的适用性如下:

  • 非常适合

    • 实时应用:如在线聊天、直播弹幕、在线协作工具等。
    • API 服务:构建 RESTful API 或作为 API 网关/BFF 层。
    • 单页应用 (SPA) 服务端渲染 (SSR):如使用 Next.js 等框架。
    • 前端工具链:大多数现代前端框架(如 React, Vue)的构建工具都依赖 Node.js。
  • 不太适合

    • CPU 密集型任务:如大数据分析、AI 模型训练、复杂算法等,这类任务更适合使用 Python 或 Go。
    • 对稳定性要求极高的复杂系统:如金融核心系统,传统上更倾向于使用 Java 等语言。

🤔 优势与劣势分析

优势 说明
高并发处理能力 事件驱动、非阻塞 I/O 模型,非常适合 I/O 密集型应用(如聊天室、API 网关、实时推送)。
生态丰富 (NPM) 拥有全球最大的开源包管理器 NPM,几乎任何功能都能找到现成的库。
全栈语言统一 前后端均使用 JavaScript,降低上下文切换成本,便于全栈开发。
劣势 说明
CPU 密集型瓶颈 单线程模型,如果执行大量复杂计算(如视频转码),会阻塞事件循环,导致服务响应变慢。
稳定性风险 未捕获的异常可能导致整个进程崩溃。生产环境通常需要使用 PM2 等工具进行进程守护。
类型安全问题 JavaScript 是弱类型语言,大型项目容易因类型错误产生 Bug(建议使用 TypeScript 弥补)。

koa

Koa 是一个由 Express.js 原班人马打造的、基于 Node.js 的下一代 Web 框架。它的设计目标是成为一个更小、更富有表现力、更健壮的框架。

Koa依赖node v7.6.0或ES2015及更高版本和async方法支持。

✨ 核心特点

  • 轻量级设计:Koa 的核心非常小巧,不内置任何中间件。这使得开发者可以按需选择组件,保持应用的精简和灵活。
  • 现代化的异步处理 :Koa 利用 ES6 的 async/await 语法,让异步代码的编写和阅读都像同步代码一样清晰,有效避免了"回调地狱"问题。
  • 优雅的洋葱模型:这是 Koa 最具特色的中间件机制。

🧅 洋葱模型中间件

与 Express 的线性模型不同,Koa 的中间件采用"洋葱模型"(Onion Model)。请求处理流程就像穿过一个洋葱:

  1. 请求进入:从外层中间件开始,一层层向内执行。
  2. 核心处理:到达最内层,执行核心业务逻辑。
  3. 响应返回:从内向外,反向执行之前中间件的后续代码。

这种双向流动的机制,使得在请求处理前后执行统一逻辑(如日志记录、错误处理、响应时间计算)变得非常优雅和高效。

javascript 复制代码
// 示例:洋葱模型执行顺序
app.use(async (ctx, next) => {
  //上下文对象 ctx(context) 包含了与当前http请求相关的所有信息,如:
  //http方法、url、请求头、请求体、查询参数等

  console.log('1. 请求进入中间件1');
  await next(); // 将控制权交给下一个中间件
  console.log('4. 响应返回中间件1');
});

app.use(async (ctx, next) => {
  console.log('2. 请求进入中间件2');
  await next(); //若中间件调用了next(),会暂停当前中间件的执行,将控制权传递给下一个中间件
  console.log('3. 响应返回中间件2');
});

app.use(async (ctx) => {
  console.log('核心业务逻辑处理');
  ctx.body = 'Hello Koa!';
});

// 控制台输出顺序: 1 -> 2 -> 核心业务逻辑处理 -> 3 -> 4

🧩 常用生态

由于 Koa 本身非常精简,实际开发中需要借助丰富的第三方中间件生态。以下是一些常用的中间件:

  • 路由 : koa-router@koa/router
  • 请求体解析 : koa-bodyparser
  • 静态文件服务 : koa-static
  • 日志记录 : koa-logger
  • 跨域处理 : @koa/cors
  • 文件上传 : @koa/multer

🚀 快速上手

安装

初始化配置文件 package.json

npm init -y //-y 是默认所有选项

使用 npm 安装 Koa 核心包:

npm install koa

2、创建一个简单应用-- server.js

一个最简单的Koa应用只需要几行代码:

javascript 复制代码
// koa ------ 基于Node.js平台的下一代web开发框架
// 运用node.js中的koa框架创建一个http服务器

const Koa = require('koa'); //引入koa模块
const app = new Koa();  //实例化一个Koa对象,实例化是指根据一个类创建具体对象的过程

//中间件
app.use(async ctx => {
  ctx.body = 'Hello World';
});

//启动成功,访问端口号3000
app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

运行

注意:

全局安装 nodemon,当nodemon在package.json设置需检测的文件,当该文件发生更改时,会自动重新启动应用

npm i nodemon -g

例如:我需要检测server.js 文件,设置**"dev":"nodemon server.js"**

{

"name": "nodeserve",

"version": "1.0.0",

"description": "",

"main": "server.js",

"scripts": {

"dev":"nodemon server.js",

"test": "echo \"Error: no test specified\" && exit 1",

"start": "node server.js"

},

"keywords": [],

"author": "",

"license": "ISC",

"type": "commonjs",

"dependencies": {

"koa": "^3.2.0"

}

}

然后运行 npm run dev,当server.js文件修改后,在网页看输出效果时,直接刷新就好,不用重新执行命令 node server.js

app.use(function)

app.use() 方法用于注册中间件。中间件http请求和响应的函数。当一个请求到达服务器时,会从第一个中间件开始执行,直到最后一个中间件。

app.use() 返回 this,因此可以链式表达。

app.use(someMeddleware)

app.use(someOtherMeddleware)

app.listen(3000)

它等同于

app.use(someMeddleware)

.use(someOtherMeddleware)

.listen(3000)

app.listen(···)

Koa应用程序不是HTTP服务器的1对1展现。可以将一个或多个Koa应用程序安装在一起形成具有单个HTTP服务器的更大应用程序。

app.listen(3000)

上方的写法 是下方写法的语法糖

const http = require('http');

http.createServer(app.callback()).listen(3000);

🧩安装核心依赖

我们需要安装 Koa 框架本身,以及一些最常用的中间件来构建一个完整的 Web 服务。

  • koa: 核心框架。
  • @koa/router: 用于处理 URL 路由。
  • koa-bodyparser: 用于解析 POST 请求的 JSON 数据体。
  • koa-static: 用于提供静态文件(如图片、CSS、JS 文件)。

npm install koa @koa/router koa-bodyparser koa-static

vue3+vite

1、安装框架

pnpm create vite 项目名

2、安装 Router、Axios、Element Plus

✅ Vue Router 4 对应 Vue 3

✅ Element Plus 是 Vue 3 官方适配版本

npm install vue-router@4 axios element-plus

3、配置 Vue Router

1️⃣ 创建路由文件

src/router/index.js

javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about/:id',
    name: 'About',
    component: () => import('../views/About.vue')
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

src/view/Home.vue

javascript 复制代码
<script setup>
import { ref } from 'vue'
import {useRouter} from 'vue-router'
import HelloWorld from '../components/HelloWorld.vue'

const router = useRouter()

function goPage(){
    router.push('/about?name=Jen');

    // Query参数(路径查询)
    // router.push({ path: '/about', query: { name: 'Jen' } });

    // Params参数(动态路由)
    // router.push({ name: 'About', params: { id: 123 } });
    // 需在路由配置中定义:{ path: '/about/:id', component: About }
}


</script>

<template>
    <div>
        <div>首页</div>
        <HelloWorld />
        <!-- 路径跳转 -->
        <div><router-link to="/about">router-link不带参数跳转</router-link></div>

        <!-- 命名路由跳转 -->
        <div><router-link :to="{ name: 'About', params: { id: 123 } }">命名路由跳转并params带参</router-link></div>

        <!-- 带查询参数 -->
        <div><router-link :to="{ path: '/about', query: { plan: 'private' } }">带查询query参数</router-link></div>

        <div><button @click="goPage">通过js跳转页面About</button></div>
    </div>
</template>

src/view/About.vue

javascript 复制代码
<script setup>
import {useRoute} from 'vue-router'
import { ref } from 'vue'

const route = useRoute()  //获取上个路由传递的参数
const count = ref(0)

console.log('query',route.query);
console.log('params',route.params);

</script>

<template>
  <div>About</div>
</template>
2️⃣ 在 main.js 中挂载 Router

src/main.js

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App)
.use(router)
.mount('#app')
3️⃣ 添加 <router-view />

src/App.vue

javascript 复制代码
<template>
  <div>
    <h1>Vue3 + Vite</h1>
    <router-view />
  </div>
</template>

4、配置 Element Plus

1️⃣ 全局引入(简单但体积大)

main.js

javascript 复制代码
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

createApp(App)
  .use(router)
  .use(ElementPlus)
  .mount('#app')

✅ 直接使用组件即可:

<el-button type="primary">按钮</el-button>

2️⃣ 按需引入(✅ 推荐)

安装自动导入插件

npm install -D unplugin-vue-components unplugin-auto-import

✅ 之后无需手动 import Element Plus 组件

配置 vite.config.js

javascript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers:[ElementPlusResolver()]
    }),
    Components({
      resolvers:[ElementPlusResolver()]
    })
  ],
  // server: {
  //   open:true,
  //   // port:8088,
  //   proxy: {
  //     '/api': {
  //       target: 'http://localhost:3000',
  //       changeOrigin: true,
  //       // rewrite: path => path.replace(/^\/api/, '')
  //     }
  //   }
  // }
})

一、包管理工具npm、yarn、pnpm

📊 1.1 、核心差异对比表

特性 npm Yarn (Classic/Berry) pnpm
底层机制 扁平化结构 (Flat) 扁平化 (v1) / PnP (v2+) 硬链接 + 符号链接
安装速度 较慢 (串行/优化中) 快 (并行安装) 极快 (复用全局缓存)
磁盘占用 高 (每个项目重复存储) 中 (v1) / 低 (v2 PnP) 极低 (全局复用,节省 50%+)
幽灵依赖 存在 (可访问未声明的包) 存在 (v1) / 解决 (v2 PnP) 严格解决 (无法访问未声明包)
Monorepo 支持 (Workspaces) 优秀 (Workspaces) 极佳 (原生支持,性能最强)
默认状态 Node.js 内置 需单独安装 需单独安装

🧐 1.2、深度解析:它们到底有什么不同?

1.2.1. npm (Node Package Manager)

  • 地位:Node.js 的"亲儿子",随 Node.js 一起安装,无需额外配置。
  • 原理 :采用扁平化node_modules 结构。它会把依赖提升到顶层,以解决早期版本的"嵌套地狱"问题。
  • 优点:生态最成熟,兼容性最好,几乎所有教程和工具都默认支持 npm。
  • 缺点 :磁盘占用大(不同项目会重复存储相同的包);存在"幽灵依赖"问题(即你可以在代码中引用 package.json 中未声明的包,因为 npm 把它提升到了顶层,这会导致部署时可能报错)。

1.2.2 、Yarn

  • 地位:由 Facebook 推出,旨在解决早期 npm 的性能和不一致性问题。
  • 版本分歧
    • Yarn Classic (v1) :引入了 yarn.lock 锁定版本,支持并行安装,速度比老版 npm 快很多。
    • Yarn Berry (v2/v3/v4) :架构彻底重构,推出了 Plug'n'Play (PnP) 模式。它甚至不再生成 node_modules 文件夹,而是通过映射文件告诉 Node.js 包在哪里。
  • 优点:v1 版本非常稳定;PnP 模式极致节省空间且杜绝幽灵依赖。
  • 缺点:PnP 模式虽然先进,但兼容性是最大痛点,部分老旧工具或 IDE 插件可能无法识别依赖,需要额外配置。

1.2.3、 pnpm (Performant npm)

  • 地位:后起之秀,目前社区增长最快,被誉为"兼顾速度与兼容性的最佳方案"。
  • 原理内容寻址存储 。它在全局维护一个存储库(Store),所有项目通过硬链接 (Hard Link)和符号链接 (Symbolic Link)引用这些文件。
    • 通俗理解 :如果你有 10 个项目都用了 React 18,npm 会在硬盘里存 10 份,而 pnpm 只存 1 份,其他项目都是"快捷方式"。
  • 优点
    • 省空间:磁盘占用极低,安装速度极快(尤其是第二次安装时,几乎瞬间完成)。
    • 严格隔离 :它生成的 node_modules 结构是非扁平的,你无法 require 未在 package.json 中声明的包,这倒逼代码规范,避免了"在我电脑上能跑,服务器上报错"的问题。
    • Monorepo 神器:在处理包含多个子包的大型项目(Monorepo)时,pnpm 的性能优势极其明显。
安装案例问题原因分析

pnpm 在尝试从 https://registry.npmjs.org/ 下载依赖包时,多次请求失败,最终导致安装中断。这是网络层面的连接问题,尤其常见于国内网络环境访问 npm 官方源时。

  1. 网络延迟或阻断 :你访问的是 npm 官方源 registry.npmjs.org,该域名在国内访问不稳定,常出现 DNS 解析慢、连接超时或被干扰。
  2. 代理或防火墙:如果你在公司内网或使用 VPN,可能被代理或防火墙拦截了 HTTPS 请求。
  3. pnpm 默认配置敏感pnpm 对网络超时和重试机制比 npm 更严格,并发请求多,更容易触发超时。
  4. 证书或 TLS 问题(虽然图中未显示,但常伴随出现):部分企业环境会中间人解密 HTTPS,导致 Node.js 不信任证书。

切换为国内镜像源(最有效)

这是最直接、最有效的解决方式。推荐使用 淘宝 NPM 镜像华为云镜像 。设置后,所有 pnpm install 都会自动走国内镜像,速度飞快,基本告别超时。

javascript 复制代码
# 临时使用淘宝镜像安装
pnpm install mysql2 --registry https://registry.npmmirror.com

# 或者永久设置镜像源
pnpm config set registry https://registry.npmmirror.com

💡 1.2.4、选型建议:我该用哪个?

结合 2026 年的技术环境,我的建议如下:

  1. 首选 pnpm

    • 如果你正在开启一个新项目,或者你的团队深受磁盘空间不足、安装依赖慢的困扰。
    • 如果你在使用 Monorepo 架构(如 Turborepo, Nx),pnpm 是目前的最佳拍档。
    • 它既保留了 node_modules 的兼容性(不像 Yarn PnP 那样需要折腾),又解决了 npm 的性能和空间痛点。
  2. 继续使用 npm

    • 如果你是初学者,不想在安装工具上花费额外精力。
    • 如果是维护老旧项目,且团队没有迁移计划,保持现状是最稳妥的。
  3. 使用 Yarn

    • 如果你维护的是已经在使用 Yarn Workspaces 的大型项目。
    • 如果你追求 Yarn Berry 的零安装(Zero-Installs)特性,并且有能力解决 PnP 带来的兼容性挑战。

🔄 1.2.5、如何迁移?

如果你决定从 npm 或 Yarn 切换到 pnpm,过程非常简单:

  1. 安装 pnpmnpm install -g pnpm
  2. 清理旧文件 :删除项目中的 node_modules 和锁文件 (package-lock.jsonyarn.lock)。
  3. 安装依赖 :运行 pnpm install
  4. 修改脚本 :在 package.json 中,将 npm run ...yarn ... 替换为 pnpm ...(可选,为了统一)。

pnpm 会自动生成 pnpm-lock.yaml,之后你就可以享受极速的安装体验了。

1.3、cnpm、pnpm区别

cnpmpnpm 虽然名字只有一字之差,但它们的定位、原理和解决的问题完全不同 。简单来说:pnpm 是为了"快和省",而 cnpm 是为了解决国内"网络慢"的问题。

以下是两者的详细对比分析:

1.3.1、核心区别对比

表格

特性 pnpm cnpm
核心定位 高性能包管理器 国内镜像客户端
主要解决问题 磁盘空间浪费、安装速度慢、幽灵依赖 官方源下载超时、网络不稳定
底层原理 使用硬链接内容寻址存储 本质是 npm 命令 + 淘宝镜像源参数
磁盘占用 极低 (所有项目共享同一个全局仓库) (与 npm 相同,每个项目独立复制文件)
依赖结构 严格隔离 (通过符号链接),杜绝幽灵依赖 扁平结构 (与 npm 一致),可能存在幽灵依赖
开发者 pnpm 社区 (Zoltan Kochan 等) 阿里巴巴 (淘宝前端团队)

1.3.2、深度解析

pnpm:磁盘空间的"压缩大师"

pnpm (Performant npm) 是一个完全重写的包管理器。

  • 原理 :它在全局维护一个内容寻址的存储仓库。当你安装 lodash 时,pnpm 会把文件存到全局仓库,然后在项目 node_modules 中通过硬链接指向它,而不是复制文件。
  • 优势
    1. 超级省空间 :如果你电脑上有 100 个项目都用 lodashnpm 会存 100 份,而 pnpm 只存 1 份。
    2. 安装极快:因为大部分文件是硬链接,不需要重复下载和解压。
    3. 严格的依赖结构 :它不会像 npm 那样把所有包都提升到根目录,这使得你的代码必须显式声明依赖,避免了"明明没安装却能用"的幽灵依赖问题。
cnpm:国内网络的"加速器"

cnpm 并不是一个新的包管理工具,它本质上就是 npm

  • 原理 :它是一个简单的 Shell 脚本或命令行工具,运行 cnpm install 实际上等于运行 npm install --registry=https://registry.npmmirror.com
  • 优势
    1. 解决超时 :将下载源指向国内的阿里云服务器,解决了连接 registry.npmjs.org 经常超时的问题。
    2. 零学习成本 :用法和 npm 一模一样。
  • 劣势 :它的磁盘占用和安装逻辑与 npm 完全一致,没有 pnpm 的性能优化。

1.4、最佳实践:强强联合

你其实不需要在两者之间二选一,因为它们解决的是不同维度的问题。

最推荐的方案是:使用 pnpm + 切换国内源。

这样既能享受 pnpm 的磁盘节省和速度优势,又能利用国内镜像解决网络超时问题。

1.5、pnpm 设置方法:

1.5.1、安装 pnpm (如果还没装):

npm install -g pnpm

1.5.2、设置国内源 (让 pnpm 飞起来):

pnpm config set registry https://registry.npmmirror.com

设置完成后,你既拥有了 pnpm 的高性能,又拥有了 cnpm 的网络速度,完全不再需要安装 cnpm 了。

二、CommonJS和ES Module模块化

2.1、CommonJS:服务端的同步哲学

CommonJS 是为服务器端(非浏览器环境)设计的模块化规范,Node.js 是其最典型的实现。

  • 核心思想 :采用同步加载的方式。因为在服务器端,模块文件都存储在本地磁盘,读取速度很快,同步阻塞不会成为性能瓶颈。
  • 加载时机运行时加载 。依赖关系在代码执行到 require() 语句时才被处理。
  • 语法特点
    • 使用 require() 函数来导入模块。
    • 使用 module.exportsexports 对象来导出模块。
  • 适用场景 :主要用于 Node.js 后端开发。在浏览器中直接使用会阻塞页面渲染,通常需要借助 Webpack、Browserify 等工具进行打包转换。
javascript 复制代码
// 导出:math.js (模块定义)
function add(a, b) {
  return a + b;
}
module.exports = { add };

// 导入:app.js (模块使用)
const math = require('./math.js'); // 同步加载
console.log(math.add(2, 3)); // 输出 5

2.2、走向统一:ES Module

随着 ECMAScript 2015 (ES6) 标准的发布,JavaScript 拥有了官方的模块化标准------ES Module (ESM) 。它结合了 CommonJS 的简洁语法和 AMD 的异步能力(通过动态 import()),并支持静态分析以实现 Tree Shaking 等优化,已成为现代前端和 Node.js 开发的统一标准。

javascript 复制代码
//导出: math.js (ES Module)
export function add(a, b) { return a + b; }

// 导入:app.js (ES Module)
import { add } from './math.js'; // 静态声明,编译时分析
console.log(add(2, 3));

2.3、ES Module和CommonJS有什么区别?

简单来说,CommonJS 是为服务器端(Node.js)设计的传统规范,而 ES Module 是为浏览器和服务端统一设计的现代标准。

以下是它们的核心区别:

2.3.1、语法差异

这是最直观的区别。

  • CommonJS : 使用 require() 函数导入模块,使用 module.exports 或 exports 对象导出模块。
  • ES Module : 使用 import 语句导入模块,使用 export 或 export default 语句导出模块。

2.3.2、加载机制与执行时机

这是两者最核心的区别,决定了它们的运行方式和性能。

  • CommonJS (运行时加载):

    • 动态加载 :模块在代码运行时 被加载。当执行到 require() 语句时,才会去读取、执行模块文件,并返回其 module.exports 对象。
    • 同步加载:加载过程是阻塞的,必须等待模块加载并执行完毕,后续代码才会继续执行。这在服务器端(文件在本地)不是问题,但在浏览器端(需要网络请求)会严重阻塞页面渲染。
  • ES Module (编译时加载):

    • 静态分析 :模块的依赖关系在代码编译阶段 就已经被确定。importexport 语句必须位于模块顶层,不能写在条件语句或函数内部。这使得构建工具(如 Webpack, Vite)可以在打包前就分析出完整的依赖图。
    • 异步加载 :在浏览器中,ESM 默认是异步加载的,不会阻塞 HTML 解析和页面渲染。Node.js 中也支持通过动态 import() 实现异步加载。

2.3.3、环境支持

  • CommonJS : 是 Node.js 的默认模块系统(.js 文件)。浏览器不原生支持,需要通过 Webpack 等工具打包转换。在 package.json 中设置 "type": "commonjs" 来启用。
  • ES Module : 是浏览器的原生标准 (使用 <script type="module">)。在 Node.js 中,需要通过 .mjs 扩展名或在 package.json 中设置**"type": "module"** 来启用。

2.4、核心区别速查表

特性 CommonJS (CJS) ES Module (ESM)
语法 require / module.exports import / export
加载时机 运行时加载 (动态) 编译时加载 (静态)
加载方式 同步加载 异步加载 (浏览器/动态import)
导出模式 值的拷贝 (快照) 值的引用 (实时绑定)
Tree Shaking 不支持 支持
顶层 this 指向 module.exports undefined (严格模式)
浏览器支持 需打包工具转换 原生支持

2.5、如何选择?

  • 新项目 :优先使用 ES Module。它是未来的标准,拥有更好的性能优化(Tree Shaking)、更清晰的语法和跨平台能力。
  • Node.js 旧项目/库 :可能仍需使用 CommonJS 以保持兼容性。
  • 混合使用 :在现代开发中,经常需要在两者之间互操作。ESM 可以导入 CJS 模块(通常作为默认导入),而 CJS 模块可以通过动态 import() 来异步加载 ESM 模块。

三、Koa创建HTTP服务器,路由管理

在 Koa 框架中,路由管理是构建 Web 应用的核心环节。由于 Koa 本身是一个极简的框架,它不内置路由功能 ,而是依赖第三方库来实现。目前业界最标准、最常用的方案是使用 @koa/router (它是早期 koa-router 的维护版本)。

🛠️ 3.1. 路由库安装

首先,你需要安装核心依赖。除了 koa 本身,还需要安装路由库 koa/router和用于解析 POST 请求体的中间件koa-bodyparser。

koa-router 兼容koa1 和 koa2 的历史版本

@koa/router 专为koa2设计的新版本,推荐使用

npm i @koa/router

🚀 3.2. 基础路由用法

在最简单的场景下,你可以在主文件中直接定义路由。

核心概念:

  • router.get/post/put/delete: 对应不同的 HTTP 请求方法。
  • ctx.params : 获取 URL 路径中的参数(如 /users/:id)。
  • ctx.query : 获取 URL 查询字符串参数(如 /search?q=koa)。
  • ctx.request.body : 获取 POST 请求的数据(需配合 koa-bodyparser)。
javascript 复制代码
//引入koa模块
const Koa = require('koa'); 
// 引入路由库
const Router = require('@koa/router');

const app = new Koa();  //创建应用程序实例
const router = new Router({prefix:'/api'});  //实例化一个Router对象,并设置一个路由访问前缀
// 中间件对路由的引用
app.use(router.routes()).use(router.allowedMethods());

// 向客户端提供资源
// http://localhost:3000/api/getInfo
router.get('/getInfo',ctx=> {
    ctx.body = {
        code:200,
        message:'success',
        list:[
            {id:1,value:'v1'},
            {id:2,value:'v2'},
            {id:3,value:'v3'},
        ]
    }
})

//启动成功,访问端口号3000
app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

四、中间件,koa-cors处理跨域请求

前端:http://localhost:5173/

后端接口 : http://localhost:3000/api/getInfo

🔖4.1.前端解决跨域

Vite 代理(解决跨域)

vite.config.js

javascript 复制代码
export default defineConfig({
  server: {
    open:true,
    // port:8088,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        // rewrite: path => path.replace(/^\/api/, '')
      }
    }
  }
})
javascript 复制代码
onMounted(async() => {
  try {
    const response = await axios.get('/api/getInfo');
    console.log('response',response);
    
  } catch (error) {
    console.error('获取数据失败:', error);
  }
  
})

🎗️4.2.后端解决跨域

前端不配置vite.config.js时,后端处理跨域。

4.2.1 前端简单封装个axios.js请求

javascript 复制代码
// axios 二次封装
// 1、导入核心库
import axios from "axios";
// 2、配置路径
axios.defaults.baseURL = 'http://localhost:3000'
// 3、请求拦截器
axios.interceptors.request.use(config =>config);
// 4、响应拦截器
axios.interceptors.response.use(
    res =>res,
    error=>Promise.reject(error)
);
// 5、导出
export default axios;

在 Koa2 中配置跨域(CORS)最简单、最标准的方式是使用官方推荐的中间件 @koa/cors(或者 koa2-cors)。使用 @koa/cors 中间件(强烈推荐)

以下是详细的配置步骤:

4.2.1 安装依赖

npm install @koa/cors

或安装

npm install koa2-cors

4.2.2 在项目中使用

在你的 Koa 入口文件(通常是 app.jsindex.js)中引入并配置。

1、基础配置(允许所有跨域):

javascript 复制代码
const Koa = require('koa');
const cors = require('@koa/cors');

const app = new Koa();

// 注册 cors 中间件(必须放在路由之前)
app.use(cors());

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

2、高级配置(带 Cookie 且限制域名): 如果你前端请求需要携带 Cookie(如 axios.defaults.withCredentials = true),那么跨域的 Origin 不能设置为 *,必须指定具体的域名,并且设置 credentials: true

关键配置项总结

表格

配置项 说明 注意事项
origin 指定允许跨域访问的源。 credentials: true 时,不能 设置为 *,必须是具体的域名。
credentials 是否允许浏览器携带凭证(如 Cookie)。 设置为 true 后,前端也必须设置 withCredentials: true
allowMethods 允许的 HTTP 请求方法。 通常包括 GET, POST, PUT, DELETE, OPTIONS
allowHeaders 允许的请求头字段。 通常包括 Content-Type, Authorization
maxAge 预检请求(OPTIONS)的缓存时间(秒)。 可以减少不必要的预检请求次数。
javascript 复制代码
const Koa = require('koa');
const cors = require('@koa/cors');

const app = new Koa();

app.use(cors({
    // 动态设置 Origin
    origin: function (ctx) {
        // 白名单
        const whiteList = ['http://localhost:8080', 'https://www.yourdomain.com'];
        const requestOrigin = ctx.request.header.origin;
        
        // 如果请求源在白名单内,则返回该请求源;否则不允许跨域
        if (whiteList.includes(requestOrigin)) {
            return requestOrigin;
        }
        return false; 

        // 如果想允许所有域名并且携带cookie,可以直接返回请求源(不安全,仅用于开发环境)
        // return requestOrigin || '*';
    },
    // 允许的请求头
    allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
    // 允许的请求方法
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    // 允许携带凭证(Cookie)
    credentials: true,
    // 预检请求的有效期,单位是秒,20天
    maxAge: 86400 * 20, 
}));

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
javascript 复制代码
// koa请求跨域配置(注意:一定要在路由使用前设置)
app.use(
    cors({
        origin:function(ctx){  //指定允许跨域访问的源。
            return ctx.request.headers.origin || "";  //动态获取地址  //当 credentials: true 时,不能设置为 *,必须是具体的域名。
        },
        credentials:true,
        exposeHeaders:["WWW-Authenticate","Server-Authorization"],
        allowMethods:["GET","POST","PUT","DELETE","OPTIONS"],
        allowHeaders:["Content-Type","Authorization","Accept"]
    })
)

五、API开发

5.1、GET请求

javascript 复制代码
router.get('/getInfo',ctx=> {
    ctx.body = {
        code:200,
        message:'success',
        list:[
            {id:1,value:'v1'},
            {id:2,value:'v2'},
            {id:3,value:'v3'},
        ]
    }
})

5.2、POST请求

在 Koa 中,请求体解析是一个常见的需求,但 Koa 核心库本身并不提供解析功能。你需要使用中间件来处理。

关于请求体解析,主要有两个流行的库:koa-bodyparserkoa-body。它们功能相似,但有一些关键区别。

  • koa-bodyparser : 这是最经典的请求体解析中间件,轻量且专注于解析 JSON、表单和文本数据。但它不支持文件上传
  • koa-body : 这是一个功能更全的中间件,它不仅支持 JSON 和表单解析,还支持 multipart/form-data 格式,可以直接处理文件上传 。(文件上传中间件: @koa/multer

你可以根据需求选择安装其中一个。

5.2.1 安装 koa-bodyparser (适用于纯数据解析)

安装依赖

npm install koa-bodyparser

koa-bodyparser 基础用法

javascript 复制代码
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');

const app = new Koa();

// 1. 必须在路由之前使用
app.use(bodyParser({
  // 允许解析的类型,默认为 ['json', 'form']
  enableTypes: ['json', 'form', 'text'],
  // JSON 请求体大小限制,默认 '1mb'
  jsonLimit: '2mb',
  // 表单请求体大小限制,默认 '56kb'
  formLimit: '100kb',
}));

//请求跨域设置

// 2. 定义路由
app.use(async (ctx) => {
  // 解析后的数据会挂载到 ctx.request.body 上
  console.log(ctx.request.body);
  ctx.body = { message: '数据接收成功', data: ctx.request.body };
});

app.listen(3000);

5.2.2 安装 koa-body (适用于需要文件上传的场景)

安装依赖

npm install koa-body

koa-body 进阶用法 (支持文件上传)

javascript 复制代码
const Koa = require('koa');
const { koaBody } = require('koa-body'); // 注意解构

const app = new Koa();

// 1. 使用 koa-body 中间件
app.use(koaBody({
  // 允许解析的类型
  multipart: true, // 支持 multipart/form-data (文件上传)
  urlencoded: true, // 支持 application/x-www-form-urlencoded
  json: true,      // 支持 application/json
  text: true,      // 支持 text/plain
  
  // 限制请求体大小
  jsonLimit: '2mb',
  formLimit: '100kb',
  
  // 文件上传相关配置
  // formidable: { uploadDir: './uploads' } // 可以配置 formidable 的选项,如上传目录
}));

// 2. 定义路由
app.use(async (ctx) => {
  // 无论是 JSON 数据还是表单数据,都可以通过 ctx.request.body 获取
  console.log(ctx.request.body);
  
  // 如果是文件上传,文件信息会在 ctx.request.body.files 中
  // 注意:koa-body 的不同版本,文件信息的位置可能略有不同,请参考其官方文档
  if (ctx.request.body.files) {
      console.log('上传的文件:', ctx.request.body.files);
  }

  ctx.body = { message: '请求处理成功', data: ctx.request.body };
});

app.listen(3000);

5.2.3 两者核心要点与对比

  1. 中间件顺序 : 无论使用哪个库,都必须 在定义路由 (router.routes()) 之前使用,否则无法解析请求体。
  2. 数据获取 : 解析后的数据都会挂载在 ctx.request.body 上,你可以像访问普通对象一样访问它。
  3. 功能选择 :
    • 如果你的项目不需要文件上传koa-bodyparser 足够用且更轻量。
    • 如果你的项目需要处理文件上传 ,请直接使用 koa-body,它集成了 formidable 库的功能,一站式解决。

表格

特性 koa-bodyparser koa-body
JSON 解析 ✅ 支持 ✅ 支持
表单解析 ✅ 支持 ✅ 支持
文件上传 不支持 支持
主要依赖 co-body formidable
适用场景 纯 API 数据交互 需要处理文件上传的复杂表单

六、用 Node.js 包 mysql2 来读写 MySQL 数据库

在 Node.js 中处理 MySQL 数据库,通常涉及安装驱动建立连接执行 SQL 语句 以及处理数据这几个核心步骤。

虽然原生的 mysql 包很常用,但我强烈建议使用 mysql2,因为它支持 Promise(配合 async/await 使用更方便)、性能更好且支持连接池。

6.1、安装驱动

npm install mysql2

6.2、 建立数据库连接

在生产环境中,我们通常使用连接池而不是单例连接。连接池可以复用连接,提高并发性能,避免频繁创建和销毁 TCP 连接的开销。

创建一个数据库配置文件(例如 db.js):

javascript 复制代码
const mysql = require('mysql2/promise'); // 引入 promise 版本

// 创建连接池
const pool = mysql.createPool({
  host: 'localhost',      // 数据库地址
  user: 'root',           // 用户名
  password: '123456',     // 密码
  database: 'test_db',    // 数据库名
  port: 3306,             // 端口
  waitForConnections: true,
  connectionLimit: 10,    // 最大连接数
  queueLimit: 0
});

module.exports = pool;

6.3、 执行 CRUD 操作 (增删改查)

在 Node.js 中操作 MySQL,最核心的是使用 参数化查询 (使用 ? 占位符),这能有效防止 SQL 注入攻击

6.3.1、🔍 查询数据

使用 pool.query 执行 SELECT 语句。

javascript 复制代码
const pool = require('./db'); // 引入上面的连接池

async function getUsers() {
  try {
    const [rows] = await pool.query('SELECT * FROM users WHERE status = ?', ['active']);
    console.log('查询结果:', rows);
    return rows;
  } catch (err) {
    console.error('查询失败:', err);
  }
}

6.3.2、✏️ 更新数据

使用 UPDATE 语句。

javascript 复制代码
async function updateUser(id, newName) {
  const sql = 'UPDATE users SET name = ? WHERE id = ?';
  const values = [newName, id];
  
  try {
    const [result] = await pool.query(sql, values);
    console.log('更新影响的行数:', result.affectedRows);
  } catch (err) {
    console.error('更新失败:', err);
  }
}

6.3.3、📝 插入数据

使用 INSERT 语句,并通过对象或数组传递数据。

javascript 复制代码
async function addUser(userData) {
  const sql = 'INSERT INTO users (name, email, age) VALUES (?, ?, ?)';
  const values = [userData.name, userData.email, userData.age];
  
  try {
    const [result] = await pool.query(sql, values);
    console.log('插入成功,ID:', result.insertId);
    return result;
  } catch (err) {
    console.error('插入失败:', err);
  }
}

6.3.4、🗑️ 删除记录

使用 DELETE 语句。

javascript 复制代码
async function deleteUser(id) {
  const sql = 'DELETE FROM users WHERE id = ?';
  
  try {
    const [result] = await pool.query(sql, [id]);
    console.log('删除影响的行数:', result.affectedRows);
  } catch (err) {
    console.error('删除失败:', err);
  }
}

6.4、结合 Koa 使用

结合 Koa 项目,你可以这样封装一个 API 接口:

javascript 复制代码
const Koa = require('koa');
const Router = require('@koa/router');
const pool = require('./db'); // 引入数据库连接池

const app = new Koa();
const router = new Router();

// 获取用户列表接口
router.get('/users', async (ctx) => {
  try {
    const [rows] = await pool.query('SELECT id, name, email FROM users');
    ctx.body = {
      code: 200,
      list: rows
    };
  } catch (error) {
    ctx.status = 500;
    ctx.body = { code: 500, message: '数据库查询错误' };
  }
});

// 添加用户接口
router.post('/users', async (ctx) => {
  const { name, email } = ctx.request.body;
  try {
    const [result] = await pool.query('INSERT INTO users (name, email) VALUES (?, ?)', [name, email]);
    ctx.body = {
      code: 200,
      message: '添加成功',
      id: result.insertId
    };
  } catch (error) {
    ctx.status = 500;
    ctx.body = { code: 500, message: '添加失败' };
  }
});

app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);

6.5、 关键注意事项

  1. 防止 SQL 注入 :永远不要使用字符串拼接来构建 SQL 语句(例如 'SELECT * FROM users WHERE id = ' + userId)。必须 使用 ? 占位符和参数数组。
  2. 连接池管理 :使用 createPool 而不是 createConnection,这样 Node.js 会自动管理连接的复用和释放。
  3. 错误处理 :数据库操作是异步的,务必使用 try...catch 捕获错误,避免程序崩溃。
  4. 环境配置 :数据库的账号密码等敏感信息,建议使用 dotenv 包读取 .env 文件,不要硬编码在代码里。
相关推荐
吴声子夜歌1 小时前
Vue3——组件基础
前端·javascript·vue.js
Southern Wind2 小时前
Vue 3 + Socket.io 实时聊天项目完整开发文档
前端·javascript·vue.js
甄心爱学习2 小时前
【项目实训(个人4)】
前端·vue.js·python
qq_419854052 小时前
clip-path绘制倾斜角裁剪的矩形占比条;基于svg实现仪表盘弧线占比图。
前端·javascript·vue.js
网络点点滴3 小时前
Node.js-填充模板
node.js
英俊潇洒美少年5 小时前
Vue2/Vue3 vue-i18n完整改造流程(异步懒加载+后端接口请求)
前端·javascript·vue.js
空中海11 小时前
第七章:vue工程化与构建工具
前端·javascript·vue.js
zhensherlock12 小时前
Protocol Launcher 系列:Trello 看板管理的协议自动化
前端·javascript·typescript·node.js·自动化·github·js
旷世奇才李先生14 小时前
Vue3\+Vite\+Pinia实战:企业级后台管理系统完整实现(附源码)
vue.js