从零开始创建简单 koa2 应用

创建一个 koa2 工程

mkdir hello-koa2 && cd hello-koa2

npm init

yarn add koa

通过以上命令行创建一个文件夹 hello-koa2,并进入该文件夹,然后我们初始化一个项目并安装 koa。
接下来用编辑器(如vscode)打开项目,创建文件app.js,输入以下代码:(当然,最好使用 git 方式管理项目)

const Koa = require('koa');
const app = new Koa();

// 对于任何请求,app 将调用该异步函数处理请求:
app.use(async(ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
});

app.listen(3000);

此时通过命令 node app.js 我们就可以项目可以运行了,浏览器打开http://localhost:3000/就可以看到内容了。
我们可以把该命令放到 package.json 的 scripts 下,后续可以一步一步构建一个完善的项目框架。

创建 middleware

什么我们已经使用 app.use 方法处理请求了,我们在该函数中使用了await next()方法,我们知道 koa2 是洋葱圈模型。当一个请求过来的时候,会依次经过各个中间件进行处理,中间件跳转的信号是await(yield) next,当到某个中间件后,该中间件处理完不执行await next的时候,然后就会逆序执行前面那些中间件剩下的逻辑,组成一个处理链。
根据这个逻辑,我们在处理过程中自己添加 middleware,做一些我们想做的事情。以下是摘自网上的一个示例:

app.use(async (ctx, next) => {
    console.log(`${ctx.request.method} ${ctx.request.url}`); // 打印URL
    await next(); // 调用下一个middleware
});

app.use(async (ctx, next) => {
    const start = new Date().getTime(); // 当前时间
    await next(); // 调用下一个middleware
    const ms = new Date().getTime() - start; // 耗费时间
    console.log(`Time: ${ms}ms`); // 打印耗费时间
});

app.use(async (ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
});

处理 URL

在以上的代码中我们对所有 URL 返回的是一样的内容,实际情况下不可能如此。正常情况下,我们应该对不同的 URL 调用不同的处理函数,返回不同的结果。例如:

app.use(async (ctx, next) => {
    if (ctx.request.path === '/') {
        ctx.response.body = 'HOME page';
    } else {
        await next();
    }
});

app.use(async (ctx, next) => {
    if (ctx.request.path === '/user') {
        ctx.response.body = 'USER page';
    } else {
        await next();
    }
});

我们可以使用 koa-router 这个 middleware,让它负责处理 URL 映射。安装 koa-router

yarn add koa-router

然后把 app.js 改造如下:

const Koa = require('koa');
const app = new Koa();
const Router = require('koa-router');
const router = new Router();

app.use(async(ctx, next) => {
    console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
    await next();
});

router.get('/hello/:name', async(ctx, next) => {
    let name = ctx.params.name;
    ctx.response.body = `<h1>Hello, ${name}!</h1>`;
});

router.get('/', async(ctx, next) => {
    ctx.response.body = '<h1>Home page</h1>';
});

app.use(router.routes());

app.listen(3000);

然后我们可以看到请求根页面//hello/:name显示的内容就不一样了。

处理 post 请求

上面我们用到的都是 get 请求,接下来我们试一下 post 请求。
我们使用koa-bodyparser组件来解析我们获取的 request 对象。 安装yarn add koa-bodyparser完之后在 app.js 中引入 koa-bodyparser。由于middleware的顺序很重要,这个koa-bodyparser必须在router之前被注册到app对象上。

const bodyParser = require('koa-bodyparser');

// 在合适的位置加上
app.use(bodyParser());

接下来我们就可以测试 post 请求了。同理,patch、delete、put等请求也可以由 router 处理。

router.post('/login', async (ctx, next) => {
    let
        name = ctx.request.body.name || '',
        password = ctx.request.body.password || '';
    console.log(`login with name: ${name}, password: ${password}`);
    if (name === 'hello' && password === '123456') {
        ctx.response.body = `<h1>Welcome, ${name}!</h1>`;
    } else {
        ctx.response.body = `<h1>Login failed!</h1>
        <p><a href="/">Try again</a></p>`;
    }
});

调整项目结构

随着 URL 请求数量的越来越多,app.js 就会越来越长,这样不利于项目的管理和拓展。
如果能把 URL 处理函数集中到特定 js 文件,然后让 app.js 自动导入所有处理 URL 的函数。这样,代码一分离,逻辑就显得清楚了。我们可以在根目录下创建 controllers 文件夹,按照分类把各 url 处理函数放在该目录下的不同 js 文件。
例如我们可以在 controllers 目录下创建 hello.js 文件,内容如下:

let fn_hello = async(ctx, next) => {
    let name = ctx.params.name;
    ctx.response.body = `<h1>Hello, ${name}!</h1>`;
};

module.exports = {
    'GET /hello/:name': fn_hello
};

app.js 修改:

const files = fs.readdirSync(__dirname + '/controllers');

// 过滤出.js文件:
const js_files = files.filter((file)=>{
    return file.endsWith('.js');
});

// 处理每个js文件:
for (let f of js_files) {
    console.log(`process controller: ${f}...`);
    // 导入js文件:
    let mapping = require(__dirname + '/controllers/' + f);
    for (var url in mapping) {
        if (url.startsWith('GET ')) {
            // 如果url类似"GET xxx":
            let path = url.substring(4);
            router.get(path, mapping[url]);
            console.log(`register URL mapping: GET ${path}`);
        } else if (url.startsWith('POST ')) {
            // 如果url类似"POST xxx":
            let path = url.substring(5);
            router.post(path, mapping[url]);
            console.log(`register URL mapping: POST ${path}`);
        } else {
            // 无效的URL:
            console.log(`invalid URL: ${url}`);
        }
    }
}

使用模板引擎

模板引擎有很多,比如 pug, ejs, nunjucks...,这里以 nunjucks 为例,配合 koa-views 中间件。
我们需要创建一个 views 文件夹,用来存放我们的目标文件。

 app.use(views(path.join(__dirname, './views'), {
    map: {
        html: 'nunjucks'
    },
    extension: 'html'
}));

// 简单用法如下
app.use(async(ctx, next) => {
    if (ctx.request.path === '/hello') {
        let name = 'Koa2'
        await ctx.render('hello', {
            name,
        })
    } else {
        await next();
    }
});

使用其他中间件

创建 public 目录,用来存放公共资源,如 css, js, 图片, 字体等资源,使用koa-static

const static = require('koa-static');

app.use(static(path.join(__dirname, './public')));
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容