Koa 使用总结

文章目录

Koa 使用总结

概述

Koa.js 是一个由 Express 团队开发的现代、轻量级 Web 框架,它使用 ES2017 的 async/await 语法,提供了更优雅的中间件处理方式。Koa 的设计理念是 "精简",它只提供最核心的功能,其余功能通过中间件实现。

Koa官网

koa-router

koa-body

环境搭建

安装依赖

复制代码
npm install koa koa-router koa-body koa-static
npm install nodemon --save-dev
  • koa:核心库
  • koa-router:路由中间件
  • koa-body:请求体解析中间件,支持文件上传
  • koa-static:静态文件服务
  • nodemon:开发环境下自动重启服务器

package.json

复制代码
{
  "name": "koademo",
  "version": "1.0.0",
  "type": "module"
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "koa": "^3.1.1",
    "koa-body": "^7.0.1",
    "koa-router": "^14.0.0",
    "koa-static": "^5.0.0"
  },
  "devDependencies": {
    "nodemon": "^3.1.11"
  }
}

使用 ESM 模块系统时注意:

  • 使用 import 和 export 替代 require 和 module.exports
  • __dirname__filename 不可用,需要使用 import.meta.url 获取

GET请求处理

处理简单请求

javascript 复制代码
import Koa from "koa";
import KoaRouter from "koa-router";

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

router.get("/api/users", async (ctx) => {
    ctx.body = [
        {name: "小白", age: 18},
        {name: "小黑", age: 28}
    ];
});

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

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

处理查询参数

javascript 复制代码
import Koa from "koa";
import KoaRouter from "koa-router";

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

router.get("/api/user", async (ctx) => {
    const {name, age} = ctx.query;
    ctx.body = {name, age};
});

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

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

处理路径参数

javascript 复制代码
import Koa from "koa";
import KoaRouter from "koa-router";

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

router.get("/api/user/:name/:age", async (ctx) => {
    const {name, age} = ctx.params;
    ctx.body = {name, age};
});

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

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

POST请求处理

koa-body

处理请求体需要使用 koa-body 中间件,Koa 是用于解析请求体的强大工具,支持多种数据格式,包括:

  • JSON 格式:application/json
  • 表单数据:application/x-www-form-urlencoded
  • 多部分表单数据:multipart/form-data(用于文件上传)

koa-body 中间件会根据请求头中的 Content-Type 自动选择合适的解析方式,无需手动配置。

javascript 复制代码
app.use(koaBody({
  multipart: true,           // 启用多部分表单数据解析
  urlencoded: true,          // 启用 x-www-form-urlencoded 解析
  json: true,                // 启用 JSON 解析
  text: false,               // 禁用纯文本解析
  encoding: 'utf-8',         // 编码格式
  formLimit: '56kb',         // 表单数据大小限制
  jsonLimit: '1mb',          // JSON 数据大小限制
  textLimit: '1mb',          // 文本数据大小限制
  formidable: {
    uploadDir: path.join(__dirname, 'uploads'), // 文件上传目录
    keepExtensions: true,    // 保留文件扩展名
    maxFileSize: 200 * 1024 * 1024, // 文件大小限制
    onFileBegin: (name, file) => {
      console.log(`开始上传文件: ${file.originalFilename}`);
    }
  }
}));

处理表单和JSON格式的数据

无论客户端发送哪种格式的数据,解析后的数据都会存放在 ctx.request.body 中,可以直接访问和使用。

javascript 复制代码
import Koa from "koa";
import KoaRouter from "koa-router";
import koaBody from "koa-body";

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

app.use(koaBody());

router.post("/api/user", async (ctx) => {
    const contentType = ctx.request.headers['content-type'];
    console.log(contentType); // 输出格式
    const {name, age} = ctx.request.body;
    ctx.status = 200;
    ctx.body = {name, age};
});


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

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

文件处理

单文件上传

javascript 复制代码
import fs from "fs";
import Koa from "koa";
import koaBody from "koa-body";
import KoaRouter from "koa-router";
import path from "path";
import {fileURLToPath} from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

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

// 确保上传目录存在
const uploadDir = path.join(__dirname, "uploads");
if (!fs.existsSync(uploadDir)) {
    fs.mkdirSync(uploadDir);
}

const uploadBody = koaBody({
    multipart: true,           // 启用多部分表单数据解析
    urlencoded: true,          // 启用 x-www-form-urlencoded 解析
    json: true,                // 启用 JSON 解析
    text: false,               // 禁用纯文本解析
    encoding: "utf-8",         // 编码格式
    formLimit: "56kb",         // 表单数据大小限制
    jsonLimit: "1mb",          // JSON 数据大小限制
    textLimit: "1mb",          // 文本数据大小限制
    formidable: {
        uploadDir: uploadDir,    // 文件上传目录
        keepExtensions: true,    // 保留文件扩展名
        maxFileSize: 200 * 1024 * 1024, // 文件大小限制 200MB
        onFileBegin: (name, file) => {
            console.log(`开始上传文件: ${file.originalFilename}`);
        }
    }
});

router.post("/api/single-upload", uploadBody, async (ctx) => {
    const files = ctx.request.files;
    if (!files) {
        ctx.status = 400;
        ctx.body = {
            err: "请选择上传文件"
        };
        return;
    }
    const {file} = files;
    const originalName = file.originalFilename;
    const newName = file.newFilename;
    ctx.body = {
        originalName,
        newName,
        size: file.size,
        type: file.mimetype,
        filePath: file.filepath,
        fileUrl: `/api/download/${newName}`
    };
});

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

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

多文件上传

javascript 复制代码
import fs from "fs";
import Koa from "koa";
import koaBody from "koa-body";
import KoaRouter from "koa-router";
import path from "path";
import {fileURLToPath} from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

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

const uploadDir = path.join(__dirname, "uploads");
if (!fs.existsSync(uploadDir)) {
    fs.mkdirSync(uploadDir);
}

const uploadBody = koaBody({
    multipart: true,           // 启用多部分表单数据解析
    urlencoded: true,          // 启用 x-www-form-urlencoded 解析
    json: true,                // 启用 JSON 解析
    text: false,               // 禁用纯文本解析
    encoding: "utf-8",         // 编码格式
    formLimit: "56kb",         // 表单数据大小限制
    jsonLimit: "1mb",          // JSON 数据大小限制
    textLimit: "1mb",          // 文本数据大小限制
    formidable: {
        uploadDir: uploadDir,    // 文件上传目录
        keepExtensions: true,    // 保留文件扩展名
        maxFileSize: 200 * 1024 * 1024, // 文件大小限制 200MB
        onFileBegin: (name, file) => {
            console.log(`开始上传文件: ${file.originalFilename}`);
        }
    }
});

router.post("/api/multiple-upload", uploadBody, async (ctx) => {
    const files = ctx.request.files;
    if (!files) {
        ctx.status = 400;
        ctx.body = {
            err: "请选择上传文件"
        };
        return;
    }
    const fileArray = Array.isArray(files.files) ? files.files : [files.files];
    const results = fileArray.map(file => {
        const originalName = file.originalFilename;
        const newName = file.newFilename;
        return {
            originalName,
            newName,
            size: file.size,
            type: file.mimetype,
            filePath: file.filepath,
            fileUrl: `/api/download/${newName}`
        };
    });
    ctx.body = results;
});

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

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

文件下载

javascript 复制代码
import fs from "fs";
import Koa from "koa";
import koaBody from "koa-body";
import KoaRouter from "koa-router";
import path from "path";
import {fileURLToPath} from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

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

// 确保上传目录存在
const uploadDir = path.join(__dirname, "uploads");
if (!fs.existsSync(uploadDir)) {
    fs.mkdirSync(uploadDir);
}

router.get(`/api/download/:filename`, async (ctx) => {
    const {filename} = ctx.params;
    const filePath = path.join(uploadDir, filename);
    if (!fs.existsSync(filePath)) {
        ctx.status = 404;
        ctx.body = {
            err: "文件不存在"
        };
        return;
    }

    // 设置响应头
    ctx.set("Content-Type", "application/octet-stream");
    ctx.set("Content-Disposition", `attachment; filename="${filename}"`);
    ctx.set("Content-Length", fs.statSync(filePath).size);
    ctx.body = fs.createReadStream(filePath);
});

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

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

响应头说明:

  • Content-Type: application/octet-stream: 表示二进制流,浏览器会触发下载
  • Content-Disposition: attachment; filename="${filename}": 告诉浏览器以附件形式下载,并指定文件名
  • Content-Length: 文件大小,帮助浏览器显示下载进度

路由

简单路由

javascript 复制代码
import Router from "koa-router";

const router = new Router();

// 基础路由示例
router.get('/', async (ctx) => {
    ctx.body = 'Hello Koa!';
});

// 注册路由到应用
app.use(router.routes()).use(router.allowedMethods());

路由分组

javascript 复制代码
const userRouter = new KoaRouter({prefix: "/api/user"});
userRouter.get("/getUsers", async (ctx) => {
    ctx.body = [
        {name: "小白", age: 18},
        {name: "小黑", age: 28}
    ];
});
userRouter.post("/createUser", async (ctx) => {
    const {name, age} = ctx.request.body;
    ctx.body = {name, age};
});
const articleRouter = new KoaRouter({prefix: "/api/article"});
articleRouter.get("/getArticles", async (ctx) => {
    ctx.body = [
        {articleName: "红楼梦", author: "曹雪芹"},
        {articleName: "三国演义", author: "罗贯中"},
    ];
});
articleRouter.post("/createArticle", async (ctx) => {
    const {articleName, author} = ctx.request.body;
    ctx.body = {articleName, author};
});

app.use(userRouter.routes()).use(userRouter.allowedMethods());
app.use(articleRouter.routes()).use(articleRouter.allowedMethods());

中间件

中间件执行流程

Koa 中间件采用洋葱模型,请求从外层中间件进入,进过内层中间件处理后,再从内层中间件返回外层中间件。

复制代码
请求 -> 中间件1 -> 中间件2 -> 处理 -> 中间件2 -> 中间件1 -> 响应

注册顺序

中间件的执行顺序决定了请求处理的流程:

  1. 全局中间件:放在最前面,如:错误处理、日志记录等
  2. 第三方中间件:如:koa-body、koa-static等
  3. 路由中间件:放在最后面,用于处理具体的请求
javascript 复制代码
// 正确的中间件顺序
app.use(globalErrorHandler);  // 全局错误处理(最先)
app.use(loggerMiddleware);    // 日志记录
app.use(koaBody());           // 请求体解析
app.use(serveStatic);         // 静态文件服务
app.use(router.routes());     // 路由处理(最后)
app.use(router.allowedMethods());

自定义日志中间件

javascript 复制代码
const loggerMiddleware = async (ctx, next) => {
    const startTime = Date.now();
    const {method, url} = ctx.request;
    console.log(`收到请求:${method} ${url}`);
    try {
        await next();
        const endTime = Date.now();
        const responseTime = endTime - startTime;
        const statusCode = ctx.status;
        console.log(`响应完成:${method} ${url} | 状态:${statusCode} | 耗时:${responseTime}ms`);
    } catch (error) {
        const endTime = Date.now();
        const responseTime = endTime - startTime;
        console.error(`请求出错:${method} ${url} | 耗时:${responseTime}ms | 错误:${error.message}`);
        throw error;
    }
};
app.use(loggerMiddleware);

404中间件

方式一

需要将 404 处理中间件放在所有路由之后。

javascript 复制代码
import fs from "fs";
import Koa from "koa";
import koaBody from "koa-body";
import KoaRouter from "koa-router";
import path from "path";
import {fileURLToPath} from "url";

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

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

// 404处理
app.use((ctx, next) => {
    if (ctx.status === 404) {
        ctx.body = {
            err: "接口不存在"
        };
    }
    return next();
});

const PORT = 3001;
app.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}`);
});
方式二

需要将 404 处理中间件放在所有路由之前。

javascript 复制代码
const errorMiddleware = async (ctx, next) => {
    try {
        await next();
        // 处理404
        if (ctx.status === 404) {
            ctx.status = 404;
            ctx.body = {
                success: false,
                err: "请求的资源不存在"
            };
        }
    } catch (error) {
        // 捕获所有错误
        console.error("全局错误捕获:", {
            message: error.message,
            stack: error.stack,
            path: ctx.request.url,
            method: ctx.request.method,
        });
        const statusCode = ctx.status || 500;
        const err = error.message || "服务器错误";
        ctx.status = statusCode;
        ctx.body = {
            success: false,
            err
        };
    }
};
app.use(errorMiddleware);

全局错误处理中间件

javascript 复制代码
import Koa from "koa";
import koaBody from "koa-body";
import KoaRouter from "koa-router";

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

router.get("/api/users", async (ctx) => {
    ctx.body = [{name: "小白", age: 18}, {name: "小黑", age: 28}];
    throw Error("发生异常了");
});

const errorMiddleware = async (ctx, next) => {
    try {
        await next();
    } catch (error) {
        // 捕获所有错误
        console.error("全局错误捕获:", {
            message: error.message,
            stack: error.stack,
            path: ctx.request.url,
            method: ctx.request.method,
        });
        const statusCode = ctx.status || 500;
        const err = error.message || "服务器错误";
        ctx.status = statusCode;
        ctx.body = {
            success: false,
            err
        };
    }
};

app.use(errorMiddleware); 

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

const PORT = 3001;
app.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}`);
});
相关推荐
suoh's Blog4 小时前
安装node低版本报错:The system cannot find the file specified.
node·nvm·node安装
爱学英语的程序员3 天前
让AI 帮我做了个个人博客(附提示词!)
人工智能·git·vue·github·node·个人博客
27669582929 天前
dy x-tt-session-dtrait 逆向分析
python·node·dy·dy逆向·抖音请求头逆向·session-dtrait·dtrait
xiangxiongfly91511 天前
Node http
http·node·文件上传·请求·文件下载·响应
aiguangyuan14 天前
Nest 与 TypeORM Cli 集成
node·后端开发·nest
启扶农17 天前
lecen:一个更好的开源可视化系统搭建项目--介绍、搭建、访问与基本配置--全低代码|所见即所得|利用可视化设计器构建你的应用系统-做一个懂你的人
低代码·vue·node·所见即所得·表单设计·页面可视化·页面设计器
weixin_5316518120 天前
Node.js 流操作
node.js·node·stream
FE阿祖23 天前
koa学习
koa·js·ndoe
亚林瓜子23 天前
nodejs里面的百分号解码之URLSearchParams
开发语言·javascript·ecmascript·node·url·百分号编码