实用指南:Fastify【实用教程】含开源范例项目

简介

官网 https://fastify.dev/

Fastify 是一款高性能的 Node.js Web 框架【后端开发框架】,适合有高性能需求的项目。

框架核心特点与设计理念适用场景生态与社区性能表现学习曲线
Express轻量灵活,极简核心,中间件机制成熟,API 简洁直观快速开发小型应用、API 服务,学习入门,原型验证最丰富,插件和工具生态极完善基础性能良好,无过多封装损耗
KoaExpress 团队打造,采用 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

"scripts": {
"dev": "chcp 65001 && set NODE_OPTIONS=--enable-source-maps --no-warnings && node --watch src/index.js"
},

原理:添加了 -watch
同时解决了终端中文乱码的问题

环境变量配置 .env

  1. 安装依赖
npm i dotenv
  1. 导入使用
    src\index.js

    import 'dotenv/config';
    // 加载.env文件
  2. 配置环境变量
    新建 .env

    PORT=3000
  3. 使用环境变量

    process.env.PORT

注册插件

src\plugins\index.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

import { registerPlugins
} from './plugins/index.js';
await registerPlugins(app);

此处的 app 为 Fastify 的实例

const app = Fastify({
logger: true
})

连接数据库

连接 MongoDB

  1. 安装插件 @fastify/mongodb

    npm i @fastify/mongodb --save
  2. 注册插件
    见上文注册插件相关的代码

  3. 配置环境变量(根据自己的环境修改)
    .env

    MONGODB_URI=mongodb://localhost:27017
    MONGODB_DB=test

开发接口

新增

src\apis\user.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 + "失败",
});
}
}
);

修改

// 更新
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 + "失败",
});
}
});

查询

// 列表
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 + "列表失败",
});
}
});

删除

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

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

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: "服务器错误,删除图片失败",
});
}
});
}

范例项目(开源地址)

https://gitee.com/sunshine39/fastify_server

posted @ 2025-09-03 16:35  wzzkaifa  阅读(43)  评论(0)    收藏  举报