新一代 node.js web 开发框架 Koa 零基础入门学习笔记

6 篇文章 0 订阅
2 篇文章 0 订阅

Koa

新一代 node.js web 开发框架。

搭建服务器

node 初始化 package.json

npm init -y

参数:
-y 全部使用默认选项设置,不再一步一步确认。

{
  "name": "RyeKoa",
  "version": "1.0.0",
  "description": "Koa 零基础入门学习笔记",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "觀·自在",
  "license": "ISC"
}

安装 Koa 模块

cnpm install koa --save 简写 npm i koa -S

参数:
--save 将在 package.json 中加入该安装包的版本信息

// package.json
{
  "name": "RyeKoa",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "koa": "^2.13.1"
  }
}

创建应用入口文件 app.js

最简实现
// app.js
const Koa = require('koa')

const app = new Koa()

app.use(async (ctx, next) => {
    ctx.response.body = 'Hello Koa!'
})

app.listen(3000)

这就是最简版的实现。

Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。
app.use() 函数用来注册中间件。
ctx 是 Koa 上下文
next 是中间件必须的,调用 next() 用来执行下一个中间件,此处没用使用,是因为 ctx.response.body 默认调用了 next()

以上代码中使用中间件也可以简写做以下形式:

app.use(async ctx => {
	// 这是 Response 别名
    ctx.body = 'Hello Koa!'
})
上下文

Koa Context 将 node 的 requestresponse 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 这些操作在 HTTP 服务器开发中频繁使用,它们被添加到此级别而不是更高级别的框架,这将强制中间件重新实现此通用功能。

每个 请求都将创建一个 Context,并在中间件中作为接收器引用,或者 ctx 标识符,如以下代码片段所示:

app.use(async ctx => {
  ctx; // 这是 Context
  ctx.request; // 这是 koa Request
  ctx.response; // 这是 koa Response
});

为方便起见许多上下文的访问器和方法直接委托给它们的 ctx.requestctx.response ,不然的话它们是相同的。 例如 ctx.bodyctx.typectx.length 委托给 response 对象,ctx.path 和 ctx.method 委托给 request。

Request 别名

以下访问器和 Request 别名等效:

  • ctx.header
  • ctx.headers
  • ctx.method
  • ctx.method=
  • ctx.url
  • ctx.url=
  • ctx.originalUrl
  • ctx.origin
  • ctx.href
  • ctx.path
  • ctx.path=
  • ctx.query
  • ctx.query=
  • ctx.querystring
  • ctx.querystring=
  • ctx.host
  • ctx.hostname
  • ctx.fresh
  • ctx.stale
  • ctx.socket
  • ctx.protocol
  • ctx.secure
  • ctx.ip
  • ctx.ips
  • ctx.subdomains
  • ctx.is()
  • ctx.accepts()
  • ctx.acceptsEncodings()
  • ctx.acceptsCharsets()
  • ctx.acceptsLanguages()
  • ctx.get()
Response 别名

以下访问器和 Response 别名等效:

  • ctx.body
  • ctx.body=
  • ctx.status
  • ctx.status=
  • ctx.message
  • ctx.message=
  • ctx.length=
  • ctx.length
  • ctx.type=
  • ctx.type
  • ctx.headerSent
  • ctx.redirect()
  • ctx.attachment()
  • ctx.set()
  • ctx.append()
  • ctx.remove()
  • ctx.lastModified=
  • ctx.etag=

启动服务器

node app

这样,使用 node 命令启动服务器,并访问本地地址端口 3000

浏览器访问地址:
http://127.0.0.1:3000/
http://localhost:3000/

此时,浏览器做出响应:
在这里插入图片描述

服务器自动重新部署

Node.js 开发辅助工具 nodemon

修改代码后,每次需要停止然后重启 Koa 应用,所做的修改才能生效。
使用了 nodemon 后,它会监测项目中的所有文件,一旦发现文件有改动,会自动重启应用。

安装 nodemon
npm i nodemon -g
参数
i 即 install
-g 全局安装(一般命令行使用的工具我们都执行全局安装)

启动应用:
nodemon app

这样,当你修改代码:

...
app.listen(3000, () => {
    console.log("服务器已启动,http://localhost:3000");
})
...

在控制台你会看到 nodemon 自动为你重启服务器:

$ nodemon app
[nodemon] 2.0.7
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node app.js`
[nodemon] restarting due to changes...
[nodemon] starting `node app.js`
服务器已启动,http://localhost:3000

中间件

Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。
使用 app.use() 函数用来注册中间件。
ctx 是上下文
next 是中间件必须的,调用 next() 用来执行下一个中间件,如果没有,程序将挂起在此处。

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

app.use(async (ctx, next) => {
    ctx.name = 'Koa'
    next()
})
app.use(async ctx => {
    ctx.body = `Hello, ${ctx.name}!`
})

app.listen(3000, () => {
    console.log("服务器已启动,http://localhost:3000");
})

洋葱模型

Koa 新一代 node.js web 开发框架。它最大的特点就是独特的中间件流程控制,是一个典型的洋葱模型。

下面两张图清晰的表明了一个请求是如何经过中间件最后生成响应的,在这种模式中开发和使用中间件是非常方便的。
在这里插入图片描述

在这里插入图片描述

const Koa = require('koa')

const app = new Koa()

app.use(async (ctx, next) => {
    console.log('中间件1:开始');
    ctx.name = 'Koa'
    await next()
    console.log('中间件1:结束');

})

app.use(async ctx => {
    console.log('中间件2:开始');
    ctx.body = `Hello, ${ctx.name}!`
    console.log('中间件2:结束');
})

app.listen(3000, () => {
    console.log("服务器已启动,http://localhost:3000");
})

控制台会输出:
在这里插入图片描述
怎么样,是不是有一点点感觉了。当程序运行到 await next() 的时候就会暂停当前程序,进入下一个中间件,处理完之后才会再回过头来继续处理。

Koa 中间件的实现重点

  • 上下文 ctx 的保存和传递
  • 中间件的顺序管理
  • next 的使用(注意 next() 前边的 await

自定义中间件

自定义一个中间件一点都不难。
第一步:定义一个方法

function MidTest(ctx) {
    global.console.log('执行了自定义中间件...')
}

第二部:导出这个方法

  1. 暴露一个函数
  2. 这个函数返回一个参数为 ctx 与 next 的回调函数
  3. 在回调函数中执行我们要完成的方法
module.exports = function () {
    return async function (ctx, next) {
        MidTest(ctx);
        // todo
        await next(); //运行完毕,交给下一个中间件
        // todo
    }
}

完整的中间件定义:

// ./middleware/mid-test.js
function MidTest(ctx) {
    global.console.log('执行了自定义中间件...')
}
module.exports = function () {
    return async function (ctx, next) {
        MidTest(ctx);
        await next(); //运行完毕,交给下一个中间件
    }
}

使用自定义中间件:
第一步:引入中间件

const
    MidTest = require('./middleware/mid-test')

第二部:注册中间件

app.use(MidTest())

运行应用程序,将看到自定义中间件的执行结果:
在这里插入图片描述

Koa 路由

安装路由中间件 koa-router

cnpm i koa-router -S

使用 koa-router

导入路由模块:

const Router = require('koa-router')

实例化路由(支持传递参数):

const router = new Router()

配置路由:

router.get('/', async (ctx, next) => {
    ctx.body = 'Hello, Koa Router!'
})

注册路由中间件:
路由实例 router,分别调用 router.routes() 和 router.allowedMethods() 来得到两个中间件。然后使用 app.use() 注册这两个中间件。

  • 调用router.routes()来组装匹配好的路由,返回一个合并好的中间件
  • 调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,会返回 405 Method Not Allowed501 Not Implemented
app.use(router.routes())
app.use(router.allowedMethods({
    // throw: true, // 抛出错误,代替设置响应头状态
    // notImplemented: () => '不支持当前请求所需要的功能',
    // methodNotAllowed: () => '不支持的请求方式'
}))

完整的示例代码:

// app.js
const
    Koa = require('koa'),
    Router = require('koa-router')

const
    app = new Koa(),
    router = new Router()

router.get('/', async (ctx, next) => {
    ctx.body = 'Hello, Koa Router!'
})

app.use(router.routes())

app.listen(3000, () => {
    console.log("服务器已启动,http://localhost:3000");
})
处理请求

koa-router 提供了 .get、.post、.put 和 .del 方法来处理各种请求,但实际业务上,我们大部分只会接触到 POST 和 GET,所以接下来只针对这两种请求类型来说明。

get:用于接收GET请求

router.get('/', async (ctx, next) => {
    ctx.body = 'Hello, Koa Router!'
})

post:用于接收POST请求

router.post('/signin', async (ctx, next) => {
    ctx.body = 'Hello, POST!'
})

all:用于接收GET与POST请求

router.all('/', async (ctx, next) => {
    ctx.body = 'Hello, POST!'
})
请求参数

有些时候需要从请求的 URL 上获取特定参数,主要分为两类: params 和 query 。 这两种参数获取的方式如下:

params 参数:
router.get('/:catalog/:title', async (ctx, next) => {
    ctx.body = `Hello, GET! 页面参数 catalog:${ctx.params.catalog} title:${ctx.params.title}`
})

在这里插入图片描述
在这里插入图片描述

query 参数
router.get('/user', async (ctx, next) => {
    console.log(ctx.query)
    ctx.body = `Hello, GET! 页面参数 Name:${ctx.query.name} Age:${ctx.query.age}`
})

在这里插入图片描述

在这里插入图片描述

post 参数

路由嵌套

路由前缀

路由分离

使用静态文件

设置静态目录:public,并创建文件 hello.html
在这里插入图片描述

安装: koa-static 模块

直接访问是访问不到的,同样这里我们要借助中间件 koa-static 模块
安装 koa-static 模块
npm i koa-static -S

使用:导入、配置、注册

// app.js
...
const KoaStatic = require('koa-static')
app.use(KoaStatic(__dirname + '/public'))
...

访问静态资源:
在这里插入图片描述

模板引擎

安装:koa-views

cnpm i koa-views -S
cnpm i ejs -S

编写模板

在根目录创建 views 文件夹,在 views 下创建 tpl_fruit.ejs 文件。代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <h1>
        <%=title %>
    </h1>
    <ul>
        <% fruits.forEach((fruit)=>{%>
            <li>
                <%=fruit %>
            </li>
            <%})%>
    </ul>

</body>

</html>

使用模板:导入、配置、注册

// app.js
...
const
    path = require('path'),
    views = require('koa-views')

// 注册模板引擎
app.use(views(path.join(__dirname, 'views'), {
    map: {
        ejs: 'ejs'
    }
}))

/**
 * 配置路由
 * 使用模板引擎输出
 */
router.get('/favourite', async (ctx, next) => {
    await ctx.render('tpl_fruit.ejs', {
        title: 'Hello,我喜欢的水果:',
        fruits: ['Apple', 'Watermelon', 'Strawberry']
    })
})
...

访问:http://localhost:3000/favourite
在这里插入图片描述

有趣的模板后缀名
// 注册模板引擎
app.use(views(path.join(__dirname, 'views'), {
    map: {
        love: 'ejs'
    }
}))

/**
 * 配置路由
 * 使用模板引擎输出
 */
router.get('/favourite', async (ctx, next) => {
    await ctx.render('tpl_fruit.love', {
        title: 'Hello,我喜欢的水果:',
        fruits: ['Apple', 'Watermelon', 'Strawberry']
    })
})

这样,你在自定义模板的时候,可以使用 .love 后缀名的文件:
在这里插入图片描述
我们也可以同时使用多种模板引擎:

// 注册模板引擎
app.use(views(path.join(__dirname, 'views'), {
    map: {
        love: 'ejs',
        html: 'underscore'
    }
}))

如果我们只使用一种模板引擎(通常也只用一种),那么注册模板引擎的时候,我们可以换种写法:

// 注册模板引擎
app.use(views(path.join(__dirname, 'views'), {
    extension: 'ejs'
}))

这样的好处是,我们再配置路由的时候就不需要指定模板的后缀名:

/**
 * 配置路由
 * 使用模板引擎输出
 */
router.get('/favourite', async (ctx, next) => {
    await ctx.render('tpl_fruit', {
        title: 'Hello,我喜欢的水果:',
        fruits: ['Apple', 'Watermelon', 'Strawberry']
    })
})

深入 koa-views

以上代码通过 app.use(views(path, args)) 注册视图中间件,这将返回一个参数为 ctx 与 next 的回调函数

小白理解:中间件就是一个有特殊约定的函数,此函数带有上下文 ctx 和 next 参数。

// 中间件
`async (ctx, next) => {
    ctx.name = 'Koa'
    next()
}`

我们可以直接在 app.use() 中直接注册该中间件:

// 注册中间件
app.use(async (ctx, next) => {
    ctx.name = 'Koa'
    next()
})

或者定义一个返回值为中间件的函数,然后注册该函数。

app.use(views(path, args))

这样就理解了 views(path, args) ,我们继续接着看:

回调函数给上下文对象 ctx 添加名为 render 的函数,用于渲染视图。

以下为 koa-views 源码:

function viewsMiddleware (path, {
  engineSource = consolidate,
  extension = 'html',
  options = {},
  map
} = {}) {
  return function views (ctx, next) {
    if (ctx.render) return next()

    ctx.render = function (relPath, locals = {}) {
      return getPaths(path, relPath, extension)
        .then((paths) => {
          // do something ...
        })
    }

    return next()
  }
}

源码中,请注意:ctx.render = function (relPath, locals = {}) {} ,这条语句就是为上下文ctx添加了render方法,这样我们就可以在 router.get() 中使用 ctx.render('tpl', {title: '使用模板引擎'}) 渲染页面了。

模板引擎 koa-art-template

art-template 中文文档
代码示例:

// package.json
{
  "name": "RyeKoa",
  "version": "1.0.0",
  "description": "学习资源",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "Rye",
  "license": "ISC",
  "dependencies": {
    "art-template": "^4.13.2",
    "koa": "^2.13.1",
    "koa-art-template": "^1.1.1",
    "koa-router": "^10.0.0",
    "koa-static": "^5.0.0",
    "koa-views": "^7.0.1"
  }
}
// app.js
const
    Koa = require('koa'),
    Router = require('koa-router'),
    KoaStatic = require('koa-static'),
    views = require('koa-views'),
    path = require('path'),
    render = require('koa-art-template')

const
    app = new Koa(),
    router = new Router()

/**
 * 访问静态资源从根目录开始, 而不是 public
 * 例如:http://localhost:3000/hello.html
 */
app.use(KoaStatic(path.join(__dirname, 'public')))

/**
 * 配置模板引擎
 * 在 Koa 实例 app 上注册
 * 并为上下文 ctx 添加 render 方法
 */
render(app, {
    root: path.join(__dirname, 'views'),
    extname: '.html'
})

// 配置路由
router.get('/', async ctx => {
    ctx.render('user', {
        name: 'dy',
        age: '2'
    })
})

// 合并配置的路由,并注册路由中间件,以及允许的请求方式
app.use(router.routes()).use(router.allowedMethods({
    // throw: true, // 抛出错误,代替设置响应头状态
    // notImplemented: () => '不支持当前请求所需要的功能',
    // methodNotAllowed: () => '不支持的请求方式'
}))

app.listen(3000, () => {
    console.log('服务器已启动,http://localhost:3000');
})
<!--
./views/user.html
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户信息</title>
</head>

<body>
    <h1>用户信息页</h1>
    <p>姓名:{{name}}</p>
    <p>年龄:{{age}}</p>
</body>

</html>

好用的“模板继承”和“子模板”

我们通常有母版页的需求,将通用的 js、css 以及固定不变的 head、footer 等部分放入母版页,母版页被其它有着以上共同特性的页面继承,这样方便统一管理和修改。

Bootstrap 是全球最受欢迎的前端框架之一,用于方便、快速的构建响应式、移动设备优先的网站。
我们的示例目录如下:
在这里插入图片描述

在静态资源文件夹 public 中放入我们需要引入的 bootstrap 资源文件,这需要你为自己的站点安装并配置 koa-static 模块

<!--
__layout.html
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{block 'title'}}学习资源{{/block}}-三好生</title>
    <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
    {{block 'head'}}{{/block}}
</head>

<body>
    <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
        <a class="navbar-brand text-info" href="#">小学Bus</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault"
            aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarsExampleDefault">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="#">首页 <span class="sr-only">(current)</span></a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">资源链接</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">下载</a>
                </li>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown"
                        aria-expanded="false">科目</a>
                    <div class="dropdown-menu" aria-labelledby="dropdown01">
                        <a class="dropdown-item" href="#">英语</a>
                        <a class="dropdown-item" href="#">数学</a>
                        <a class="dropdown-item" href="#">语文</a>
                        <a class="dropdown-item" href="#">科学</a>
                    </div>
                </li>
            </ul>

            <a class="btn btn-info" href="#">资源链接</a>
        </div>
    </nav>

    <header>{{block 'header'}}{{/block}}</header>
    <main>
        {{block 'content'}}页面内容{{/block}}
    </main>
    <footer class="container">
        <p>@三好生 {{block 'footer'}}{{/block}}</p>
    </footer>

    <script src="/jquery.slim.min.js"></script>
    <script src="/bootstrap/js/bootstrap.bundle.min.js"></script>
</body>

</html>

子模板:

<!--links.html-->
<a href="#">我的博客</a> | <a href="#">在线云学习</a> | <a href="#">AI 学习助手</a>

首页:
{{extend './__layout.html'}}继承母版页
{{include '__links.html'}}导入子模板
{{block 'title'}}{{title}}{{/block}}放入自定义内容

<!--index.html-->
{{extend './__layout.html'}}

{{block 'title'}}{{title}}{{/block}}

{{block 'head'}}
<style>
    .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
    }

    @media (min-width: 768px) {
        .bd-placeholder-img-lg {
            font-size: 3.5rem;
        }
    }

    /* Move down content because we have a fixed navbar that is 3.5rem tall */
    body {
        padding-top: 3.5rem;
    }
</style>
{{/block}}

{{block 'content'}}
<div class="jumbotron">
    <div class="container">
        <h1 class="display-3">Hello, 三好生!</h1>
        <p>丰富的学习资源,助力成为三好生......</p>
        <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more &raquo;</a></p>
    </div>
</div>

<div class="container">
    <!-- Example row of columns -->
    <div class="row">
        <div class="col-md-4">
            <h2>语文</h2>
            <p>爱语文</p>
            <p><a class="btn btn-secondary" href="#" role="button">开始学习 &raquo;</a></p>
        </div>
        <div class="col-md-4">
            <h2>数学</h2>
            <p>爱数学</p>
            <p><a class="btn btn-secondary" href="#" role="button">开始学习 &raquo;</a></p>
        </div>
        <div class="col-md-4">
            <h2>英语</h2>
            <p>爱英语</p>
            <p><a class="btn btn-secondary" href="#" role="button">开始学习 &raquo;</a></p>
        </div>
    </div>

    <hr>

</div> <!-- /container -->
{{/block}}

{{block 'footer'}}
{{include '__links.html'}}
{{/block}}

最后的效果:
在这里插入图片描述

Koa 跨域设置

使用中间件编写跨域设置:

app.use((ctx, next) => {
    // 设置允许跨域
    ctx.set("Access-Control-Allow-Origin", "*");
    ctx.set("Access-Control-Allow-Methods", "PUT, POST, GET, DELETE, OPTIONS");
    // 请求头设置
    ctx.set(
        "Access-Control-Allow-Headers",
        `Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild,x-token,sessionToken,token`
    );
    if (ctx.method == "OPTIONS") {
        ctx.body = 200;
    } else {
        next();
    }
})

配置服务器支持断点续传

配置 Koa 静态资源支持断点续传 Accept-Ranges 和 Content-Range

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值