新一代 node.js web 开发框架。
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"
}
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
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 的 request
和 response
对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 这些操作在 HTTP 服务器开发中频繁使用,它们被添加到此级别而不是更高级别的框架,这将强制中间件重新实现此通用功能。
每个 请求都将创建一个 Context,并在中间件中作为接收器引用,或者 ctx
标识符,如以下代码片段所示:
app.use(async ctx => {
ctx; // 这是 Context
ctx.request; // 这是 koa Request
ctx.response; // 这是 koa Response
});
为方便起见许多上下文的访问器和方法直接委托给它们的 ctx.request
或 ctx.response
,不然的话它们是相同的。 例如 ctx.body
、ctx.type
和 ctx.length
委托给 response 对象,ctx.path 和 ctx.method 委托给 request。
以下访问器和 Request 别名等效:
以下访问器和 Response 别名等效:
node app
这样,使用 node 命令启动服务器,并访问本地地址端口 3000
浏览器访问地址:
http://127.0.0.1:3000/
或 http://localhost:3000/
修改代码后,每次需要停止然后重启 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() 的时候就会暂停当前程序,进入下一个中间件,处理完之后才会再回过头来继续处理。
自定义一个中间件一点都不难。
第一步:定义一个方法
function MidTest(ctx) {
global.console.log('执行了自定义中间件...')
}
第二部:导出这个方法
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())
cnpm i koa-router -S
导入路由模块:
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() 注册这两个中间件。
405 Method Not Allowed
或 501 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 。 这两种参数获取的方式如下:
router.get('/:catalog/:title', async (ctx, next) => {
ctx.body = `Hello, GET! 页面参数 catalog:${ctx.params.catalog} title:${ctx.params.title}`
})
router.get('/user', async (ctx, next) => {
console.log(ctx.query)
ctx.body = `Hello, GET! 页面参数 Name:${ctx.query.name} Age:${ctx.query.age}`
})
设置静态目录:public,并创建文件 hello.html
直接访问是访问不到的,同样这里我们要借助中间件 koa-static 模块
安装 koa-static 模块
npm i koa-static -S
// app.js
...
const KoaStatic = require('koa-static')
app.use(KoaStatic(__dirname + '/public'))
...
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>Documenttitle>
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']
})
})
以上代码通过 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: '使用模板引擎'})
渲染页面了。
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');
})
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 模块。
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="#">小学Busa>
<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>
子模板:
<a href="#">我的博客a> | <a href="#">在线云学习a> | <a href="#">AI 学习助手a>
首页:
{{extend './__layout.html'}}
继承母版页
{{include '__links.html'}}
导入子模板
{{block 'title'}}{{title}}{{/block}}
放入自定义内容
{{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 »a>p>
div>
div>
<div class="container">
<div class="row">
<div class="col-md-4">
<h2>语文h2>
<p>爱语文p>
<p><a class="btn btn-secondary" href="#" role="button">开始学习 »a>p>
div>
<div class="col-md-4">
<h2>数学h2>
<p>爱数学p>
<p><a class="btn btn-secondary" href="#" role="button">开始学习 »a>p>
div>
<div class="col-md-4">
<h2>英语h2>
<p>爱英语p>
<p><a class="btn btn-secondary" href="#" role="button">开始学习 »a>p>
div>
div>
<hr>
div>
{{/block}}
{{block 'footer'}}
{{include '__links.html'}}
{{/block}}
使用中间件编写跨域设置:
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