专栏名称: xiangzhihong
前端跨平台工程师、客户端工程师
目录
相关文章推荐
湖南日报  ·  连相亲都帮办,难怪老百姓都爱 ·  3 天前  
新闻株洲  ·  这样的“止血小英雄”,不关注一下吗? ·  3 天前  
51好读  ›  专栏  ›  xiangzhihong

Koa2开发快速入门

xiangzhihong  · 掘金  ·  · 2019-02-21 14:25

正文

阅读 80

Koa2开发快速入门

Koa2入门

创建Koa2

首先,我们创建一个名为koa2的工程目录,然后使用VS Code打开。然后,我们创建app.js,输入以下代码:

// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');

// 创建一个Koa对象表示web app本身:
const app = new Koa();

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

// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');
复制代码

对于每一个http请求,koa将调用我们传入的异步函数进行处理。例如:

async (ctx, next) => {
    await next();
    // 设置response的Content-Type:
    ctx.response.type = 'text/html';
    // 设置response的内容:
    ctx.response.body = '<h1>Hello, koa2!</h1>';
}
复制代码

其中,参数ctx是由koa传入的封装了request和response的变量,我们可以通过它访问request和response,next是koa传入的将要处理的下一个异步函数。 那么,怎么启动koa呢?首先,你需要安装koa,可以直接使用npm进行安装,可以参考 Koa官网资料

在这里插入图片描述
然后在刚才的koa的项目目录中新建一个package.json,这个文件用于管理koa项目运行需要的依赖包,依赖时注意koa版本号。例如:

{
    "name": "hello-koa2",
    "version": "1.0.0",
    "description": "Hello Koa 2 example with async",
    "main": "app.js",
    "scripts": {
        "start": "node app.js"
    },
    "keywords": [
        "koa",
        "async"
    ],
    "author": "xzh",
    "license": "Apache-2.0",
    "repository": {
        "type": "git",
        "url": "https://github.com/michaelliao/learn-javascript.git"
    },
    "dependencies": {
        "koa": "2.7.0"
    }
}
复制代码

其中,dependencies是我们的工程依赖的包以及版本号,需要注意版本号的对应。其他字段均用来描述项目信息,可任意填写。然后,在koa目录下执行npm install安装项目所需依赖包。安装完成后,项目的目录结构如下:

hello-koa/
|
+- .vscode/
|  |
|  +- launch.json        //VSCode 配置文件
|
+- app.js              //使用koa的js
|
+- package.json          //项目配置文件
|
+- node_modules/     //npm安装的所有依赖包
复制代码

然后,使用npm start启动项目,即可看到效果。

在这里插入图片描述
当然,还可以直接用命令node app.js在命令行启动程序,该命名最终执行的是package.json文件中的start对应命令:

"scripts": {
    "start": "node app.js"
}
复制代码

接下来,让我们再仔细看看koa的执行逻辑,核心代码如下:

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

每收到一个http请求,koa就会调用通过app.use()注册的async函数,并传入ctx和next参数。那为什么需要调用await next()呢? 原因是koa把很多async函数组成一个处理链,每个async函数都可以做一些自己的事情,然后用await next()来调用下一个async函数,此处我们把每个async函数称为中间件。

例如,可以用以下3个middleware组成处理链,依次打印日志,记录处理时间,输出HTML。

// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');

// 创建一个Koa对象表示web app本身:
const app = new Koa();

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>';
});

// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');
复制代码

koa-router

在上面的例子中,我们处理http请求一律返回相同的HTML,这样显得并不是很友好,正常的情况是,我们应该对不同的URL调用不同的处理函数,这样才能返回不同的结果。

为了处理URL跳转的问题,我们需要引入koa-router中间件,让它负责处理URL映射。首先在package.json中添加koa-router依赖:

"koa-router": "7.4.0"
复制代码

然后用npm install安装依赖。接下来,我们修改app.js,使用koa-router来处理URL映射。

const Koa = require('koa');

// 注意require('koa-router')返回的是函数:
const router = require('koa-router')();

const app = new Koa();

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

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

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

app.use(router.routes());
app.listen(3000);
console.log('app started at port 3000...');
复制代码

需要说明的是,require('koa-router') 返回的是函数,其作用类似于:

const fn_router = require('koa-router');
const router = fn_router();
复制代码

然后,我们使用router.get('/path', async fn)来注册一个GET请求。可以在请求路径中使用带变量的/hello/:name,变量可以通过ctx.params.name来完成访问。 当我们在输入首页: http://localhost:3000/

在这里插入图片描述
当在浏览器中输入: http://localhost:3000/hello/koa
在这里插入图片描述

post请求

用router.get('/path', async fn)处理的是get请求。如果要处理post请求,可以用router.post('/path', async fn)。

用post请求处理URL时,我们会遇到一个问题:post请求通常会发送一个表单、JSON作为request的body发送,但无论是Node.js提供的原始request对象,还是koa提供的request对象,都不提供解析request的body的功能!此时需要借助koa-bodyparser插件。

所以,使用koa-router进行post请求时,需要在package.json中添加koa-bodyparser依赖:

"koa-bodyparser": "4.2.1"
复制代码

现在,我们就可以使用koa-bodyparser进行post请求了,例如:

const Koa = require('koa');

// 注意require('koa-router')返回的是函数:
const router = require('koa-router')();
const bodyParser = require('koa-bodyparser');

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

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

router.get('/', async (ctx, next) => {
    ctx.response.body = `<h1>Index</h1>
        <form action="/signin" method="post">
            <p>Name: <input name="name" value="koa"></p>
            <p>Password: <input name="password" type="password"></p>
            <p><input type="submit" value="Submit"></p>
        </form>`;
});

//POST请求
router.post('/signin', async (ctx, next) => {
    var
        name = ctx.request.body.name || '',
        password = ctx.request.body.password || '';
    console.log(`signin with name: ${name}, password: ${password}`);
    if (name === 'koa' && password === '12345') {
        ctx.response.body = `<h1>Welcome, ${name}!</h1>`;
    } else {
        ctx.response.body = `<h1>Login failed!</h1>
        <p><a href="/">Try again</a></p>`;
    }
});

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

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

app.listen(3000);
console.log('app started at port 3000...');
复制代码

然后,当我们使用npm start启动服务,输入koa和12345时,就能通过测试。

优化

现在,虽然我们可以根据输入处理不同的URL,但是代码的可阅读和扩展性极差。正确的写法是页面和逻辑分离,于是我们把url-koa复制一份,重命名为url2-koa,并重构项目。重构的项目目录结构如下:

url2-koa/
|
+- .vscode/
|  |
|  +- launch.json 
|
+- controllers/
|  |
|  +- login.js        //处理login相关URL
|  |
|  +- users.js      //处理用户管理相关URL
|
+- app.js           //使用koa的js
|
+- package.json 
|
+- node_modules/          //npm安装的所有依赖包
复制代码

我们在controllers目录下添加一个index.js文件,并添加如下内容:

var fn_index = async (ctx, next) => {
    ctx.response.body = `<h1>Index</h1>
        <form action="/signin" method="post">
            <p>Name: <input name="name" value="koa"></p>
            <p>Password: <input name="password" type="password"></p>
            <p><input type="submit" value="Submit"></p>
        </form>`;
};

var fn_signin = async (ctx, next) => {
    var
        name = ctx.request.body.name || '',
        password = ctx.request.body.password || '';
    console.log(`signin with name: ${name}, password: ${password}`);
    if (name === 'koa' && password === '12345') {
        ctx.response.body = `<h1>Welcome, ${name}!</h1>`;
    } else {
        ctx.response.body = `<h1>Login failed!</h1>
        <p><a href="/">Try again</a></p>`;
    }
};

module.exports = {
    'GET /': fn_index,
    'POST /signin': fn_signin
};
复制代码

上面示例中,index.js通过module.exports把两个URL处理函数暴露出来。 然后,我们修改app.js,让它自动扫描controllers目录,找到所有的js文件并注册每个URL。

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

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

// 处理每个js文件:
for (var 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":
            var path = url.substring(4);
            router.get(path, mapping[url]);
            console.log(`register URL mapping: GET ${path}`);
        } else if (url.startsWith('POST ')) {
            // 如果url类似"POST xxx":
            var path = url.substring(5);
            router.post(path, mapping[url]);
            console.log(`register URL mapping: POST ${path}`);
        } else {
            // 无效的URL:
            console.log(`invalid URL: ${url}`);
        }
    }
}
复制代码






请到「今天看啥」查看全文