普通视图

发现新文章,点击刷新页面。
昨天以前首页

入坑node.js全面指南(三)

作者 温暖前端
2025年6月30日 21:15

前两篇文章详细讲解了node.js的知识点,本篇文章来实现一个经典的全栈待办事项(Todo List)应用,采用前后端分离架构。后端基于 Node.js + Express + TypeScript,前端基于 Vue 3 + TypeScript,数据持久化采用 MongoDB。项目结构清晰,适合学习和实践全栈开发。

项目源码地址链接: todolist-express: 一个基于 Vue3 + TypeScript + Node.js + Express + MongoDB 的全栈 ToDoList 项目。

项目截图

image.png

image.png

image.png

一、后端技术栈与实现

1. 技术选型

  • Node.js:高性能的 JavaScript 运行环境,适合 I/O 密集型应用。
  • Express:简洁灵活的 Web 框架,便于快速搭建 RESTful API。
  • TypeScript:为 JavaScript 提供类型系统,提升代码可维护性和可读性。
  • Mongoose:MongoDB 的对象建模工具,简化数据库操作。

2. 目录结构

backend/
  ├── src/
  │   ├── app.ts         // Express 应用主入口
  │   ├── index.ts       // 启动服务
  │   ├── models/        // Mongoose 数据模型
  │   └── routes/        // 路由模块

3. 主要知识点

a) TypeScript 在后端的应用

  • 类型定义提升了开发效率,减少了运行时错误。
  • 接口(interface)用于定义数据结构,如 Todo、User。
import mongoose, { Schema, Document } from 'mongoose';

export interface ITodo extends Document {
  content: string;
  status: 'active' | 'completed' | 'deleted';
  priority: number;
  tags: string[];
  category: string;
  user: mongoose.Types.ObjectId;
  createdAt: Date;
  completedAt?: Date;
}

b) 路由与中间件

  • 路由模块化,routes/auth.ts 处理认证相关接口,routes/todo.ts 处理待办事项相关接口。
  • 使用中间件进行请求体解析、鉴权等操作。
// 获取所有 ToDo
router.get('/', auth, async (req: AuthRequest, res: Response) => {
  const todos = await Todo.find({ user: req.user.id, status: { $ne: 'deleted' } }).sort({ createdAt: -1 });
  res.json(todos);
});

c) Mongoose 数据建模

  • models/Todo.tsmodels/User.ts 定义了数据结构和约束。
  • 通过 Schema 约束字段类型、必填项、默认值等。

d) 用户认证

  • 通常采用 JWT(JSON Web Token)进行用户身份验证(可在 auth.ts 路由中实现)。
  • 注意密码加密存储(如 bcrypt),避免明文存储。
// 鉴权中间件
function auth(req: AuthRequest, res: Response, next: NextFunction) {
  const token = req.headers['authorization']?.split(' ')[1];
  if (!token) return res.status(401).json({ msg: '未登录' });
  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    req.user = decoded;
    next();
  } catch {
    res.status(401).json({ msg: '无效token' });
  }
}

e) 错误处理

  • 统一的错误处理机制,保证接口返回一致的错误格式,便于前端处理。

  • Vue 3:响应式、组件化的前端框架。

  • TypeScript:提升代码可维护性。

  • Vite:新一代前端构建工具,极快的热更新体验。

  • Pinia/Vuex:状态管理。

二、前端技术栈与实现

2. 目录结构

frontend/
  ├── src/
  │   ├── api/           // 封装后端请求
  │   ├── components/    // 复用组件
  │   ├── store/         // 状态管理
  │   ├── views/         // 页面视图
  │   └── router/        // 路由配置

3. 主要知识点

a) 组件化开发

  • 复用性强,易于维护,如 TodoItem.vueTodoList.vue
  • 父子组件通信(props、emit)、兄弟组件通信(状态管理)。

b) 状态管理

  • store/todo.tsstore/user.ts 管理全局状态,避免多层组件传参。
  • 响应式数据驱动视图更新。

c) 路由管理

  • router/index.ts 配置页面路由,实现页面跳转与权限控制。

d) API 封装

  • api/request.ts 封装 axios/fetch,统一处理请求和响应。
  • api/todo.tsapi/user.ts 封装具体业务接口,便于维护。

e) 样式与动画

  • assets/animate.css 提供动画效果,提升用户体验。

三、项目运行以及MongoDB启动指南

项目运行

  1. 安装 Node.js、MongoDB
  2. 分别进入 backend、frontend 目录执行 npm install
  3. 启动 MongoDB 服务
  4. 启动后端:npm run dev
  5. 启动前端:npm run dev
  6. 访问前端页面(如 http://localhost:5173)

MongoDB启动指南

  • 访问 MongoDB 官方下载页面
  • 选择 Windows 平台,版本建议选择最新稳定版(如 6.0)
  • 下载 MSI 安装程序(64 位)

在mongodb官网安装mongodb后,启动命令行输入mongod,仍旧显示 image.png

打开电脑的高级系统设置,配置mongodb的环境变量,将mongodb的bin目录添加到环境变量中

image.pngimage.png

启动mongodb服务的时候提示

image.png

可以通过在mongod/bin目录下cmd,输入mongod --dbpath D:\app\MongoDB\data启动数据库服务

数据库启动后,可以通过mongodb compass进行数据库连接

image.png

数据库启动并连接成功后,再运行服务端代码,服务端代码运行端如下图所示,则代表服务端代码成功运行

image.png

入坑node.js全面指南(二)

作者 温暖前端
2025年6月30日 08:21

1. Express.js 架构哲学

作为 Node.js 生态中应用最广泛的 Web 框架,Express.js 以"最小化设计原则"为核心,通过模块化架构实现灵活扩展。

核心设计理念

  • 简约而不简单:仅提供 Web 开发的基础功能,其他能力通过中间件扩展
  • 中间件驱动架构:采用洋葱模型实现请求处理流水线
  • 无强制约束:不限定项目结构,开发者拥有完全自由
  • 渐进式增强:可根据需求添加功能模块,避免过度设计

关键架构组件

  • 中间件系统:请求处理流水线,每个中间件可访问请求/响应对象
  • 路由分发器:基于 HTTP 方法和 URL 的高效路由匹配
  • 视图引擎接口:支持多种模板引擎(EJS、Pug等)
  • 扩展接口:通过 app.set()/app.get() 管理应用配置

安装

npm install express

基本应用结构

const express = require('express');
const app = express();
const port = 3000;

// 中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 路由
app.get('/', (req, res) => {
  res.send('Hello Express!');
});

// 启动服务器
app.listen(port, () => {
  console.log(`服务器运行在 http://localhost:${port}`);
});

2. 中间件

中间件类型

  1. 应用级中间件:绑定到 app 实例

    // 记录请求时间的应用级中间件
    app.use((req, res, next) => {
      req.requestTime = Date.now();
      console.log(`请求时间: ${req.requestTime}`);
      next(); // 必须调用next传递控制权
    });
    
  2. 路由级中间件:绑定到路由实例

    const router = express.Router();
    router.use((req, res, next) => {
      console.log('路由中间件执行');
      next();
    });
    
  3. 错误处理中间件:四个参数的特殊中间件

    app.use((err, req, res, next) => {
      console.error(err.stack);
      res.status(500).send('服务器错误!');
    });
    
  4. 内置中间件

    app.use(express.json()); // 解析JSON请求体
    app.use(express.static('public')); // 静态文件服务
    
  5. 第三方中间件

    const helmet = require('helmet');
    app.use(helmet()); // 安全头部设置
    

中间件执行顺序

Express 中间件按照声明顺序执行,理解这一点对于正确配置应用至关重要:

  1. 请求到达服务器
  2. 按顺序执行匹配的中间件
  3. 每个中间件可以修改 req/res 对象
  4. 中间件通过 next() 传递控制权
  5. 当某个中间件发送响应时,链终止

3. 路由系统深度解析

路由基础

// 基础路由
app.get('/products', (req, res) => {
  res.json([{ id: 1, name: '手机' }]);
});

// 路由参数
app.get('/products/:id', (req, res) => {
  const id = req.params.id;
  res.json({ id, name: `商品${id}` });
});

// 查询参数
app.get('/search', (req, res) => {
  const q = req.query.q;
  res.send(`搜索: ${q}`);
});

4. 请求与响应处理

请求对象 (req)

  • req.params:路由参数
  • req.query:查询字符串参数
  • req.body:请求体内容(需要中间件解析)
  • req.cookies:客户端 cookies
  • req.headers:HTTP 请求头
  • req.ip:客户端 IP 地址

响应对象 (res)

  • res.status(code):设置状态码
  • res.send(body):发送响应
  • res.json(obj):发送 JSON 响应
  • res.render(view, locals):渲染模板
  • res.redirect(path):重定向
  • res.set(field, value):设置响应头
// 综合使用示例
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  if (!username || !password) {
    return res.status(400).json({ error: '用户名和密码必填' });
  }
  
  // 认证逻辑...
  res.cookie('token', 'abc123', { httpOnly: true });
  res.redirect('/dashboard');
});

5. Express vs Koa vs Node 深度对比

5.1 Express 与 Node.js 的关系

关系解析

  • Node.js 是运行时环境,提供 HTTP 模块等基础能力
  • Express 是基于 Node.js HTTP 模块的封装框架
  • Express 简化了路由、中间件等常见 Web 开发任务

5.2 Express 与 Koa 的深度对比

  • 设计理念:Express.js 设计较为灵活,提供了丰富的功能和插件,适合快速开发各种类型的 Web 应用。而 Koa 是由 Express.js 的原班人马打造,它更注重简洁和优雅,采用了更现代化的异步编程方式(如 async/await),并且没有捆绑任何中间件,开发者可以根据需求自由选择和组合中间件。

  • 中间件机制:Express.js 的中间件采用传统的回调函数形式,中间件之间通过next()方法传递控制权。在处理复杂的异步操作时,可能会出现回调地狱的问题。Koa 的中间件基于 async/await 语法,通过await next()实现中间件的执行和传递,代码更加简洁、易读,避免了回调地狱。

  • 错误处理:在 Express.js 中,错误处理通常通过在中间件或路由处理函数中捕获错误,并传递给错误处理中间件。而 Koa 使用 try/catch 块或者在 async 函数中返回错误,通过onerror事件来统一处理错误,错误处理更加直观和方便。

Express vs Koa 核心差异

特性 Express Koa
中间件模型 线性链式调用 洋葱模型
异步处理 回调函数 原生Async/Await
错误处理 集中式错误中间件 Try/Catch捕获
路由系统 内置Router 需koa-router
请求/响应封装 原生对象扩展 统一Context对象
体积大小 约4.5MB 约0.6MB

6. Express 实战案例:RESTful API 实现

const express = require('express');
const app = express();
app.use(express.json());

// 内存数据库
let users = [
  { id: 1, name: '张三', email: 'zhang@example.com' },
  { id: 2, name: '李四', email: 'li@example.com' }
];

// 获取所有用户
app.get('/api/users', (req, res) => {
  res.json({
    status: 'success',
    count: users.length,
    data: users
  });
});

// 创建新用户
app.post('/api/users', (req, res) => {
  const { name, email } = req.body;
  const newUser = {
    id: users.length + 1,
    name,
    email
  };
  
  users.push(newUser);
  
  res.status(201).json({
    status: 'success',
    data: newUser
  });
});

});

// 全局404处理
app.use((req, res) => {
  res.status(404).json({
    status: 'error',
    message: '路由不存在'
  });
});

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    status: 'error',
    message: '服务器内部错误'
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`服务器运行在 http://localhost:${PORT}`);
});

7. 总结

Express.js的优势:

  1. 简洁核心+丰富扩展:保持核心轻量,通过中间件提供无限扩展
  2. 渐进式采用路径:从简单路由到复杂应用均可胜任
  3. 社区驱动生态:庞大的中间件生态系统解决各种场景需求
  4. 文档与稳定性:优秀的文档质量和稳定的API设计

最佳实践建议

  1. 使用 express.Router 实现路由模块化
  2. 中间件按功能拆分为独立模块
  3. 统一错误处理机制
  4. 使用 helmet 增强安全性
  5. 结合 morgan 记录访问日志
  6. 使用 express-validator 进行输入验证

为什么需要dependencies 与 devDependencies

作者 温暖前端
2025年6月24日 23:59

一、核心概念解析

1. dependencies(生产依赖)

  • 定义:项目在生产环境运行时必须依赖的第三方库
  • 安装方式npm install <package-name>npm install <package-name> --save
  • 典型示例
"dependencies": {
  "vue": "^3.2.0",
  "react": "^18.0.0",
  "axios": "^1.0.0"
}
  • 核心特征

    • 会被打包到最终的生产代码中
    • 直接影响应用运行功能
    • 用户浏览器中实际执行的代码

2. devDependencies(开发依赖)

  • 定义:仅在开发阶段需要的工具库
  • 安装方式npm install <package-name> --save-dev
  • 典型示例
"devDependencies": {
  "webpack": "^5.0.0",
  "eslint": "^8.0.0",
  "jest": "^29.0.0"
}
  • 核心特征

    • 不会出现在生产打包结果中
    • 仅用于开发、测试、构建过程
    • 开发者本地环境专用

二、核心区别对比

特性 dependencies devDependencies
环境要求 生产环境必须 仅开发环境需要
打包结果 包含在最终 bundle 中 不包含在生产代码中
安装影响 npm install 默认安装 需添加 --production 跳过
典型场景 框架/UI库/工具函数 构建工具/测试库/代码规范
体积影响 直接影响生产包大小 仅影响 node_modules 体积
安全要求 需严格审核(影响用户安全) 风险相对较低

二、为什么要区分依赖类型

1. 优化生产环境部署

  • 减少生产包体积:开发工具(像 Webpack、Babel)在生产环境是不需要的,如果将它们包含在生产包中,会平白增加部署包的大小,进而延长应用的加载时间。
  • 提升部署速度:只安装生产依赖能够显著加快部署速度,特别是在 CI/CD 流程中,这一点尤为重要。

2. 降低安全风险

  • 减少攻击面:开发依赖通常会直接暴露工具的版本信息,而这些信息可能会被攻击者利用。不在生产环境安装开发依赖,可以降低这种安全风险。
  • 依赖更新管理:区分依赖类型后,可以更有针对性地管理安全更新。例如,使用 npm audit 命令时,只会检查生产依赖的安全漏洞。

3. 简化项目维护

  • 清晰的依赖结构:将依赖按照用途分开,项目结构会更加清晰,团队成员能够快速了解项目的技术栈。
  • 避免版本冲突:开发依赖和生产依赖的版本需求可能不同,分开管理可以避免版本冲突。

4. 节约开发资源

  • 节省磁盘空间:在开发环境中,开发依赖和生产依赖都会被安装;而在生产环境中,只需要安装生产依赖,这样可以节省大量的磁盘空间。
  • 加速依赖安装:只安装必要的依赖(如生产环境只安装生产依赖),能够加快依赖的安装速度。

三、最佳实践

1. 合理分类依赖

  • 生产依赖:项目运行时直接使用的库,例如 React、Vue、axios 等。
  • 开发依赖:构建工具(Webpack、Vite)、编译工具(Babel、TypeScript)、测试框架(Jest、Mocha)、代码检查工具(ESLint、Prettier)等。

2. 使用语义化版本控制

package.json 中,依赖版本通常会使用语义化版本范围(如 ^1.2.3~1.2.3):

  • ^:允许升级到次要版本和补丁版本(例如 ^1.2.3 允许升级到 1.x.x 的最新版本)。
  • ~:只允许升级到补丁版本(例如 ~1.2.3 允许升级到 1.2.x 的最新版本)。

3. 定期更新依赖

  • 使用 npm outdatedyarn outdated 命令检查过时的依赖。
  • 使用 npm updateyarn upgrade 命令更新依赖。
  • 借助工具(如 Dependabot、Renovate)自动更新依赖并创建 Pull Request。

4. 锁定依赖版本

  • 使用 package-lock.jsonyarn.lock 文件锁定依赖的精确版本,确保团队成员和生产环境使用相同的依赖版本。
  • 在 CI/CD 流程中使用 npm ciyarn install --frozen-lockfile 命令保证依赖安装的一致性。

5. 特殊情况处理

有些依赖可能同时属于生产依赖和开发依赖,例如 TypeScript:

  • 如果项目需要在生产环境进行类型检查(如运行时类型验证),那么 TypeScript 可以作为生产依赖。
  • 一般情况下,TypeScript 只在开发阶段使用,所以通常将其作为开发依赖。

四、依赖分类指南

1. 判断标准

  • 是否在生产环境运行?
    如果某个库是应用程序运行的必要组成部分(如React组件、API请求库),则应放入dependencies
  • 是否仅用于开发流程?
    如果某个工具仅用于编译代码、检查格式或运行测试(如Babel、Jest),则应放入devDependencies

2.特殊情况处理

  • TypeScript类型定义

    • 如果是运行时依赖的类型(如@types/react),应放入dependencies
    • 如果是开发工具的类型(如@types/jest),应放入devDependencies
  • 构建工具的运行时依赖
    某些构建工具(如Babel)可能需要运行时辅助库(如@babel/runtime),这些应作为dependencies安装。

3. 典型依赖分类指南

依赖类型 分类 代表库
前端框架 dependencies Vue, React, Angular
状态管理 dependencies Pinia, Redux, MobX
HTTP客户端 dependencies Axios, Fetch
UI组件库 dependencies Element Plus, Ant Design
构建工具 devDependencies Webpack, Vite, Rollup
编译器/转译器 devDependencies Babel, TypeScript, SWC
代码质量工具 devDependencies ESLint, Prettier, Stylelint
测试框架 devDependencies Jest, Cypress, Mocha
CSS预处理器 devDependencies Sass, Less, PostCSS

五、总结

区分 dependenciesdevDependencies 是前端项目依赖管理的一项重要最佳实践,它能够带来以下好处:

  • 优化生产环境部署,减少包体积和部署时间。
  • 降低安全风险,减少攻击面。
  • 简化项目维护,使依赖结构更加清晰。
  • 节约开发资源,提高开发效率。
  • 精准依赖树,避免"依赖地狱"

在实际开发中,要根据依赖的用途合理分类,并遵循语义化版本控制和定期更新依赖的原则,这样才能保证项目的稳定性和可维护性。

Vuex 与 Pinia:全面解析现代 Vue 状态管理的进化之路

作者 温暖前端
2025年6月24日 23:30

一、Vuex:官方经典解决方案

1. 什么是 Vuex?

Vuex是Vue.js官方推出的状态管理模式和库,它采用集中式存储应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex的核心思想来源于Flux架构,强调单向数据流和状态变更的可追踪性。

2. 核心概念

Vuex的核心概念包括:

  • State:应用的单一数据源,类似于组件中的data
  • Getters:类似于计算属性,用于获取state的派生状态
  • Mutations:修改state的唯一途径,必须是同步操作
  • Actions:处理异步操作,提交mutations
  • Modules:将store分割成多个模块,每个模块拥有自己的state、getters、mutations和actions

3. 基础使用示例

// Vuex 示例
const store = new Vuex.Store({
  state: { count: 0 },
  mutations: {
    increment(state) { state.count++ }
  },
  actions: {
    asyncIncrement({ commit }) {
      setTimeout(() => commit('increment'), 1000)
    }
  },
  getters: {
    doubleCount: state => state.count * 2
  }
})

二、Pinia:新时代状态管理方案

1. 什么是 Pinia?

Pinia 是 Vue 核心团队开发的轻量级状态管理库,专为 Vue 3 设计。它保留了 Vuex 的核心思想,但简化了 API 设计,移除了 mutations 等冗余概念,提供更直观的开发体验。

2. 核心特性

  • 无 mutations 设计(actions 处理同步/异步)
  • 完美的 TypeScript 支持
  • 组合式 API 优先
  • 自动代码分割
  • 轻量化(1KB gzip)

与Vuex相比,Pinia最大的变化是移除了mutations,允许在actions中直接修改state,同时提供了更自然的TypeScript集成和更灵活的模块化方案。

3. 基础使用示例

// Pinia 示例
export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() { this.count++ },
    async asyncIncrement() {
      setTimeout(() => this.increment(), 1000)
    }
  },
  getters: {
    doubleCount: (state) => state.count * 2
  }
})

三、组件中使用对比

1. vuex

<script>
import { mapState, mapActions } from 'vuex'
export default {
  computed: {
    ...mapState(['count']),
    ...mapGetters(['doubleCount'])
  },
  methods: {
    ...mapActions(['asyncIncrement'])
  }
}
</script>

2. pinia

<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script>

<template>
  <button @click="counter.asyncIncrement">
    {{ counter.count }} ({{ counter.doubleCount }})
  </button>
</template>

关键差异:Pinia 无需 mapHelpers,直接通过 Store 实例访问属性和方法

四、Vuex与Pinia的适用场景

1. Vuex的适用场景

Vuex适用于以下场景:

  1. 中大型应用:当应用规模较大,组件间状态共享复杂时,Vuex的严格分层设计有助于保持代码的可维护性。
  2. 需要状态变更追踪:Vuex的mutations提供了明确的状态变更记录,便于调试和问题追踪。
  3. 与Vue 2兼容:对于仍在使用Vue 2的项目,Vuex是官方推荐的状态管理方案。
  4. 团队熟悉Flux架构:如果团队已经熟悉Flux或Redux架构,Vuex的概念更容易理解和接受。

2. Pinia的适用场景

Pinia适用于以下场景:

  1. Vue 3项目:Pinia是为Vue 3设计的,充分利用了组合式API和Proxy特性。
  2. TypeScript项目:Pinia提供了一流的TypeScript支持,减少了类型声明的工作量。
  3. 简化开发流程:Pinia去除了mutations,简化了API,使开发更加高效。
  4. 需要灵活的模块化:Pinia的store设计更加灵活,支持动态导入和按需加载。
  5. 与组合式API配合使用:Pinia的API设计与Vue 3的组合式API无缝集成,提供更自然的开发体验。

五、vuex和pinia的对比和总结

对比项 Vuex Pinia
API设计 需要定义state、getters、mutations和actions,mutations是唯一修改state的途径 只需要定义state、getters和actions,actions可直接修改state
TypeScript支持 需要额外的类型定义,尤其在模块和复杂状态结构时 天生支持TypeScript,强大的类型推导能力,减少手动类型声明
模块化与代码组织 使用modules来分割store,每个模块有自己的state、getters、mutations和actions 使用独立的store函数,每个store通过唯一的id来区分,结构更扁平
插件系统 插件主要用于监听mutations和state变更 插件可以扩展store功能,添加自定义属性和方法,提供更强大扩展能力
性能 状态变更需要经过mutations的额外步骤 去除了mutations,状态变更更加直接,性能略有提升

Pinia作为Vue生态系统中新一代的状态管理库,在多个方面优于Vuex。例如,Pinia通过去除mutations和简化API,显著减少了样板代码,使开发流程更加高效。开发者可以更专注于业务逻辑,而不是遵循严格的架构模式。它简化了API设计,提供了更好的TypeScript支持,优化了性能,并与Vue 3的组合式API无缝集成。对于新项目,特别是使用Vue 3和TypeScript的项目,Pinia应该是首选的状态管理方案。

❌
❌