简介
Fastify 是一款高性能的 Node.js Web 框架【后端开发框架】,适合有高性能需求的项目。
框架 | 核心特点与设计理念 | 适用场景 | 生态与社区 | 性能表现 | 学习曲线 |
---|---|---|---|---|---|
Express | 轻量灵活,极简核心,中间件机制成熟,API 简洁直观 | 快速开发小型应用、API 服务,学习入门,原型验证 | 最丰富,插件和工具生态极完善 | 基础性能良好,无过多封装损耗 | 低 |
Koa | Express 团队打造,采用 async/await 语法,中间件洋葱模型更优雅 | 追求代码简洁性和现代异步编程的中小型应用 | 生态良好,兼容多数 Express 中间件 | 性能略优于 Express,异步处理更高效 | 中低 |
NestJS | 基于 TypeScript,模块化架构,支持依赖注入,借鉴 Angular 设计思想 | 大型企业级应用、微服务,需要强类型和严格架构规范的项目 | 生态快速增长,官方模块丰富,TypeScript 支持完善 | 性能优秀,复杂业务场景下稳定性强 | 高 |
Fastify | 极致性能优化,基于 Schema 验证,插件系统高效,低开销 | 高并发 API 服务、需要处理大量请求的性能敏感场景 | 生态专注性能,插件轻量高效 | 性能领先,比 Express 快 2-3 倍 | 中 |
Hapi | 配置驱动开发,内置输入验证、缓存等功能,安全性强 | 构建稳定的 API 服务、企业级后端系统 | 生态完善,官方插件质量高,文档详尽 | 性能良好,配置复杂时略有损耗 | 中高 |
Sails | 全栈 MVC 框架,类似 Ruby on Rails,内置 ORM 和实时通信 | 快速开发 CRUD 应用、实时应用(如聊天、协作工具) | 生态针对性强,适合快速迭代开发 | 性能中等,封装较厚重 | 中 |
Egg.js | 基于 Koa,阿里团队维护,"约定优于配置",内置企业级最佳实践 | 中大型企业级应用、团队协作项目,尤其是需要规范开发流程的场景 | 国内社区活跃,阿里系场景验证,插件生态完善 | 性能与 Koa 接近,企业级特性加持下稳定性强 | 中 |
开发调试
热更新
但不支持 .env 文件的热更新
package.json
json
"scripts": {
"dev": "chcp 65001 && set NODE_OPTIONS=--enable-source-maps --no-warnings && node --watch src/index.js"
},
原理:添加了 -watch
同时解决了终端中文乱码的问题
环境变量配置 .env
- 安装依赖
dos
npm i dotenv
-
导入使用
src\index.js
jsimport 'dotenv/config'; // 加载.env文件
-
配置环境变量
新建 .env
envPORT=3000
-
使用环境变量
jsprocess.env.PORT
注册插件
src\plugins\index.js
js
import mongodb from '@fastify/mongodb';
export const registerPlugins = async (app) => {
// 注册 MongoDB 插件
app.register(mongodb, {
url: process.env.MONGODB_URI,
database: process.env.MONGODB_DB
});
}
src\index.js
js
import { registerPlugins } from './plugins/index.js';
ts
await registerPlugins(app);
此处的 app 为 Fastify 的实例
js
const app = Fastify({
logger: true
})
连接数据库
连接 MongoDB
-
安装插件 @fastify/mongodb
dosnpm i @fastify/mongodb --save
-
注册插件
见上文注册插件相关的代码
-
配置环境变量(根据自己的环境修改)
.env
envMONGODB_URI=mongodb://localhost:27017 MONGODB_DB=test
开发接口
新增
src\apis\user.js
js
app.post(
"/add",
{
schema: {
//可根据需要添加自定义的校验规则
},
},
async (request, reply) => {
try {
const newData = {
...request.body,
createdAt: new Date(),
};
const result = await collection.insertOne(newData);
return reply.code(201).send({
success: true,
message: config.label + "新增成功",
data: {
id: result.insertedId,
...newData,
},
});
} catch (error) {
app.log.error("新增" + config.label + "失败:", error);
return reply.code(500).send({
success: false,
error: "新增" + config.label + "失败",
});
}
}
);
修改
js
// 更新
app.post("/update/:id", async (request, reply) => {
try {
const { ObjectId } = app.mongo;
const updateData = {
...request.body,
updatedAt: new Date(),
};
const result = await collection.updateOne(
{ _id: new ObjectId(request.params.id) },
{ $set: updateData }
);
if (result.modifiedCount === 0) {
return reply.code(404).send({
success: false,
error: config.label + "不存在或未修改",
});
}
return {
success: true,
message: config.label + "更新成功",
};
} catch (error) {
app.log.error("更新" + config.label + "失败:", error);
return reply.code(500).send({
success: false,
error: "更新" + config.label + "失败",
});
}
});
查询
js
// 列表
app.get("/list", async (request, reply) => {
try {
const list = await collection.find().toArray();
return {
success: true,
count: list.length,
data: list,
};
} catch (error) {
app.log.error("获取" + config.label + "列表失败:", error);
return reply.code(500).send({
success: false,
error: "获取" + config.label + "列表失败",
});
}
});
删除
js
app.post("/delete/:id", async (request, reply) => {
try {
const { ObjectId } = app.mongo;
const result = await collection.deleteOne({
_id: new ObjectId(request.params.id),
});
if (result.deletedCount === 0) {
return reply.code(404).send({
success: false,
error: config.label + "不存在",
});
}
return {
success: true,
message: config.label + "删除成功",
};
} catch (error) {
app.log.error("删除" + config.label + "失败:", error);
return reply.code(500).send({
success: false,
error: "删除" + config.label + "失败",
});
}
});
上传图片
src\apis\upload.js
js
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
export default async function (app) {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// 从 apis 目录向上两级到达项目根目录,然后进入 uploads 目录
const uploadDir = path.join(__dirname, "..", "..", "uploads");
// 创建上传目录(如果不存在)
try {
await fs.access(uploadDir);
} catch {
await fs.mkdir(uploadDir, { recursive: true });
}
// 图片上传接口
app.post("/image", async (request, reply) => {
try {
// 获取上传的文件
const data = await request.file();
// 验证文件是否存在
if (!data) {
return reply.code(400).send({ error: "未上传图片" });
}
// 验证文件类型(仅允许图片)
const allowedMimeTypes = ["image/jpeg", "image/png", "image/webp"];
if (!allowedMimeTypes.includes(data.mimetype)) {
return reply.code(400).send({
error: "不支持的文件类型,仅允许 jpg、png、webp",
});
}
// 生成唯一文件名(避免覆盖)
const fileName = `${Date.now()}-${Math.random()
.toString(36)
.substring(2, 10)}.${data.filename.split(".").pop()}`;
const filePath = path.join(uploadDir, fileName);
// 将文件流保存到磁盘
try {
// 使用@fastify/multipart提供的toBuffer方法处理文件
const buffer = await data.toBuffer();
await fs.writeFile(filePath, buffer);
} catch (err) {
throw err;
}
// 返回图片信息
return {
success: true,
data: {
fileName,
originalName: data.filename,
mimetype: data.mimetype,
url: `/uploads/${fileName}`, // 访问图片的 URL 路径
size: await fs.stat(filePath).then((stat) => stat.size),
},
};
} catch (error) {
app.log.error("图片上传失败:", error);
return reply.code(500).send({ error: "图片上传失败" });
}
});
}
删除文件
src\apis\delFile.js
js
import path from "path";
import fs from "fs/promises";
import { fileURLToPath } from "url";
export default async function (app) {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// 从 apis 目录向上两级到达项目根目录,然后进入 uploads 目录
const uploadDir = path.join(__dirname, "..", "..", "uploads");
// 删除文件接口
app.delete("/:filename", async (request, reply) => {
try {
const { filename } = request.params;
// 验证文件名格式,防止路径遍历攻击
if (!/^[\w\-]+\.(jpg|jpeg|png|gif|webp)$/.test(filename)) {
return reply.code(400).send({
success: false,
message: "无效的文件名格式",
});
}
const filePath = path.join(uploadDir, filename);
console.log(">>>", filePath);
// 检查文件是否存在
try {
await fs.access(filePath);
} catch {
return reply.code(404).send({
success: false,
message: "图片不存在",
});
}
// 执行删除操作
await fs.unlink(filePath);
return {
success: true,
message: "图片已成功删除",
filename,
};
} catch (error) {
app.log.error("删除图片失败:", error);
return reply.code(500).send({
success: false,
message: "服务器错误,删除图片失败",
});
}
});
}
范例项目(开源地址)