基于Node.js和Koa框架搭建服务器实现API


前言:前段时间学习了如何基于Flask框架部署服务器,感觉非常容易上手,而这段时间又开始接触和学习Node.js与Koa,相比较而言它们有着很强的特性(比如异步),摸爬滚打中总算是成功学会了部署简单的服务,在此记录一下坎坷的过程。

Node.js是一个事件驱动I/O服务端JavaScript环境,它执行Javascript的速度非常快,使得 JavaScript 成为与PHP、Python等服务端语言平起平坐的脚本语言。Koa则是一个新的Web框架,是Express的下一代基于Node.js的Web框架。Koa丢弃了回调函数,并有力地增强错误处理能力。同时Koa提供了一套优雅的方法,可以快速而愉快地编写服务端应用程序。Koa2作为新版本,完全使用Promise并配合async来实现了异步。
本文基于Node.js与Koa2框架,讲解如何搭建服务器,开发实现后端API。在此之前,我们要具备一些HTML和JavaScript的基础知识和异步编程的思想,如果你已经具备了,那就开始吧!

下载与安装

Node.js 安装包及源码下载地址为:https://nodejs.org/en/download/,在其中选择对应的系统即可。安装完成后命令行输入:

node --version

显示版本号,即代表安装成功。
Koa的安装参可考官方文档,在命令行输入:

npm install koa

等待安装完成即可。
至于Node.js的开发工具,笔者习惯使用功能强大的VSCode。

最简单的应用

我们在常用的工作目录下,创建一个文件,命名为server.js,输入代码如下:

const Koa = require('koa');

const app = new Koa();
app.use(async ctx => 	// 处理请求的异步函数
{
  ctx.body = 'Hello World';
});

app.listen(8080);	// 监听端口号

console.log("Server running at http://127.0.0.1:8080");

接着在命令行输入(首先要cd进入工作目录,如果是在VSCode中,在运行界面中选择终端即可):

node server.js

结果如下图,点击链接即可访问,如下图所示。
终端显示
浏览器显示
至此就搭建好了一个最简单的服务器,对客户端发送的请求,服务端回复一个“Hello World”。
PS:在终端退出程序按Ctrl+C即可,服务相应就停止。

路由

通常情况下,我们服务器需要不同的路由来处理不同的请求,而Koa拥有一个集中处理URL的中间件:koa-router
PS:中间件(middleware)是Koa的一个重要组成部分,Koa拥有许多实用的中间件,我们安装的中间件都在工作目录下的node_modules文件夹中。中间件的执行顺序十分重要,理解了中间件,也就学会了Koa。
首先安装中间件,在命令行输入:

npm install koa-router --save

安装完成后,我们把刚才的server.js文件修改为如下:

const Koa = require('koa');
const router = require('koa-router')(); // 注意这里最后是函数调用

const app = new Koa();

// 添加路由
router.get('/', async (ctx, next) =>  // 根路由
{
    ctx.body = '我是服务器,我可以处理很多请求,对应不同的路由';
});

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

app.use(router.routes());
app.listen(8080);

console.log("Server running at http://127.0.0.1:8080");

再次运行,对应在浏览器中可以访问到不同的路由。
根路由
路由1

跨域

当域名、端口、协议有任意一个不一样的时候就会出现跨域。通常我们都希望可以跨域访问服务器,但在默认情况下,我们用Koa部署的服务器却不允许我们这么做,因此要设置可以跨域。设置跨域的方法很多,比如请求与响应的数据使用jsonp格式,而Koa里又封装了一个实现跨域的中间件:koa2-cors
首先安装中间件,在命令行输入:

 npm install koa2-cors --save

安装完成后,我们继续修改刚才的server.js文件。
在一开始引入语句后加入如下语句:

const cors = require('koa2-cors');

在最后为app增加中间件的语句中加入如下语句:

app.use(cors()); 

至此就实现了跨域了,在这里运行看不出有什么区别,但接下来我们设计客户端后,就可以在前端访问到我们的服务器了。

处理Get/Post请求

我们搭建服务器,最重要的功能就是能够接收请求、发送响应,在客户端、服务器之间顺畅地收发数据。接下来就分别讲解Get和Post两种方式如何请求与响应。
我们首先建立一个客户端页面,命名为client.html,其中代码如下:

<!doctype html>
<head>
    <meta charset="utf-8"/>
    <title>
        简单客户端页面
    </title>
</head>
<body>
    ID:<textarea id="ID" style="height:15px;width:150px;"></textarea>
    <br>
    姓名:<textarea id="Name" style="height:15px;width:150px;"></textarea>
    <br>
    <button onclick="getSubmit()">以Get方式提交</button>
    <button onclick="postSubmit()">以POST方式提交</button>
    <script>   
    // 此处存放处理事件的代码
    </script>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
    </script>
</body>

我们在客户端向服务器发送HTTP请求的方式有很多,而在这里采用的是jQuery中的$.ajax方法,其一般的调用方式如下:

$.ajax(
{
	type: "str类型", 		// 请求方法,get或者post
	url: "str类型",			// 请求的URL路径,即服务器路径
	data:					// 发送的数据
    {
         attribute: value
    },
	dataType: "str类型", 	// 收发数据的格式,通常为json或jsonp
    success: function(data)	// 请求成功的回调函数,data即服务器返回的数据
    {
 		console.log(data);
	},
    error: function(XMLHttpRequest, textStatus, errorThrown)
    // 请求错误的回调函数,参数分别为XMLHttpRequest对象、错误信息、捕获的错误对象
    {  
    	console.log(textStatus);
    }  
});

因为要用到这一方法,所以在刚才的客户端页面中,千万不要漏掉最后的一段引用jQuery的JavaScript代码。

Get方式

在Koa中,获取Get请求接收的数据的来源是request对象中的query方法或querystring方法。其中query方法返回的是格式化的参数对象,querystring方法返回的是请求字符串。而Koa又可以直接从上下文中获取这两个方法,因此在服务器端,我们在刚才的server.js文件中增加一条路由:

router.get('/getData', async (ctx, next) => 
{
    // 获取路由
    let url = ctx.url;
    //  从上下文的request对象中获取
    let request = ctx.request;
    let req_query = request.query;
    let req_queryString = request.querystring;
    //  从上下文中直接获取
    let ctx_query = ctx.query;
    let ctx_queryString = ctx.querystring;
    // 显示
    console.log(url);
    console.log(req_query);
    console.log(req_queryString);
    console.log(ctx_query);
    console.log(ctx_queryString);
    // 发送数据
    ctx.response.body = JSON.stringify("我已收到消息");
});

在刚才的client.html文件中,我们在script代码部分加入Get请求的事件处理函数:

function getSubmit()
{
    $.ajax(
    {
        type: "get",
        url: "http://127.0.0.1:8080/getData",
        data:
        {
            ID: document.getElementById("ID").value,
            Name: document.getElementById("Name").value
        },
        dataType: "json",
        success: function(data)
        {
            console.log(data);
        },
        error: function(XMLHttpRequest, textStatus, errorThrown)
        {  
            console.log(textStatus);  
        }              
    });
}

启动服务器,再在客户端页面中输入ID和姓名,点击“以Get方式”提交,可以看到URL中加上了客户端发送的数据的键值对,query和querystring方法各自可以对此解析,获取到前端以Get方式发送的数据,客户端也收到了服务器响应的信息。
Get-服务器
Get-客户端

Post方式

Koa又封装了一个处理Post请求的中间件:koa-bodyparser
首先安装中间件,在命令行输入:

npm install koa-bodyparser --save

安装完成后,同样在server.js开始的引用部分添加:

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

在最后为app添加中间件的部分添加:

app.use(cors());
app.use(bodyParser());	// 注意最后是函数调用
app.use(router.routes());

实际添加第2条即可,这里展示所有中间件是提醒大家注意中间件的顺序
接着就给server.js增加一条路由:

router.post('/postData', async (ctx, next) => 
{
    // 获取数据
    let ID = ctx.request.body.ID;
    let Name = ctx.request.body.Name;
    // 显示
    console.log(ID);
    console.log(Name);
    // 发送数据
    ctx.response.body = JSON.stringify("我已收到消息");
});

在client.html文件中,我们在script代码部分加入Post请求的事件处理函数:

function postSubmit()
{
    $.ajax(
    {
        type: "post",
        url: "http://127.0.0.1:8080/postData",
        data:
        {
            ID: document.getElementById("ID").value,
            Name: document.getElementById("Name").value
        },
        dataType: "json",
        success: function(data)
        {
            console.log(data);
        },
        error: function(XMLHttpRequest, textStatus, errorThrown)
        {  
            console.log(textStatus);  
        }                  
    });
}

启动服务器,再在客户端页面中输入ID和姓名,点击“以Post方式”提交,可以看到服务器获取到前端以Post方式发送的数据,客户端也收到了服务器响应的信息。
Post-服务器
Post-客户端
笔者在实际的部署的过程中,经常会遇到以下两个问题:
1.客户端提交后出现 404 not found error
解决:这类问题大多是由服务器没启动或者客户端请求的URL路径不正确或不存在引起的,但笔者遇到更多的情况,是服务器端处理请求(无论是Get还是Post)后没有返回数据,所以记得服务器在处理请求结束之前要返回消息。

2.客户端提交后报错:parsererrorUnexpected token xxx。
解决:这是由于服务器发送的格式与客户端请求中规定的数据格式不一致,导致了转换错误。通常情况下服务器与客户端间收发的数据都是json格式,所以在服务器返回的消息中,一定要使用ctx.response.body = JSON.stringify(发送数据),以转为json格式。而笔者遇到Unexpected token xxx 更多的原因则是客户端规定了数据格式为jsonp,而服务器返回json格式,之所以使用jsonp也是因为要实现跨域,而使用了本文中的跨域方法,就不再需要使用jsonp格式了。

至此,以上的讲解和代码你都能理解和运行,那么就已经成功地掌握了基于Node.js和Koa框架搭建服务器。

连接MySQL与异步编程

我们搭建好了服务器,能自由自在地与客户端之间收发数据,但这离一个基础的服务器还差一点。服务器的一个基本功能就是能够读取数据库,所以在这里以MySQL为例,讲解一下Koa2如何操作MySQL数据库。
首先是安装需要的模块,在命令行输入:

npm install mysql --save

安装成功后,在server.js的引入模块部分加入:

const mysql = require('mysql');

const connection = mysql.createConnection
({
  host     : 'localhost',
  user     : 'root',
  password : '*******', // 你的MySQL数据库root密码
  database : '*******'	// 要连接的数据库名
});

connection.connect();	// 打开连接

我们先对client文件进行修改,在其表单部分加入:

    <br>
    <br>
    <button onclick="search()">查询</button>
    <br>
    结果:<textarea id="selectData" style="height:150px;width:300px;"></textarea>

在其script代码部分加入:

function search()
{
    $.ajax(
    {
        type: "post",
        url: "http://127.0.0.1:8080/selectData",
        dataType: "json",
        success: function(data)
        {
            document.getElementById("selectData").innerHTML = JSON.stringify(data);
        },
        error: function(XMLHttpRequest, textStatus, errorThrown)
        {  
            console.log(textStatus);  
        }                
    });
}

再在server.js中增加一条路由:

router.post('/selectData', async (ctx, next) => 
{
    // sql语句
    let sqlStr = `select * from user;`
    // 查询
    connection.query(sqlStr, function (error, results, fields) 
    {
        if (error) 
        {
            console.log(error);
        }
        // 发送结果
        console.log(results);
        ctx.response.body = JSON.stringify(results);
      });
});

注意,在这里sql语句使用了撇引号``,而非单引号或双引号,是因为它可以进行字符串格式化操作,比如:

let ID = ctx.request.body.ID;
let sqlStr = `select * from bodydata where ID = ${ID}`;

回归正题,我们整个业务逻辑很清晰,客户端点击“查询”按钮,触发search()事件,请求获取user表中的数据,服务端查询后打印一下结果并将结果返回,客户端收到后显示在文本框selectedData中,看起来没有一点问题,但实际执行的过程中,客户端报错:
客户端报错
在之前讨论过,在路径正确的情况下,这一错误产生的原因是服务端没有返回数据,于是我们查看服务器在终端上的运行情况:
服务端输出
看到这里不禁会露出问号脸,服务器已经成功地查询到了结果,并且输出在了控制台,而紧接着的代码就是发送回这一数据,为何客户端偏偏就没收到呢?
笔者在此困扰了许久。查明原因不难,在学习Node.js时我们就了解到它异步的特性,例如我们定义3个异步函数func1()、func2()、func3(),然后执行以下代码:

func1();
func2();
func3();

Node.js执行的过程并不是顺序式的func1()、func2()、func3(),而是异步执行,也就是说执行func1()时,程序并不会就此等待它返回,而是立马接着执行它之后的代码,func2()、func3()也是如此。异步的特性使得Web进行请求和响应时有很高的并发度,响应时间也因此大大缩短。而我们服务端用于处理请求的正是异步函数,因此服务器并不会等待数据库查询函数执行完成,而是直接执行后面的代码,而后面则是处理函数体结束,服务端自然结束处理请求,什么也没返回给客户端,客户端报错,而在这之后query()执行完成,打印信息,返回结果,但此时连接已经断开。
解决这一问题就需要用到Promise对象, Promise对象使得异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
我们在server.js的引入模块部分的打开连接后加入以下语句:

query= function(sql, parmas=null)
{
    return new Promise(function(reject,resolve)
    {        
        //执行sql语句
        connection.query(sql,parmas, function (error, results, fields) 
        {
            if (error) 
            {
                throw error;
            }
            reject(results);  // 注意是reject   
        });
        // 提交
        connection.commit();
    })
}

在此定义了query()函数,它返回一个Promise对象。这里没有在query()函数中前后各打开、关闭连接,而是一直保持连接处于打开状态,因为每次开关的代价比较大,会减慢响应速度。
然后修改刚才的路由为如下:

router.post('/selectData', async (ctx, next) => 
{
    // sql语句
    let sqlStr = `select * from user;`
    // 查询
    let res = await query(sqlStr);
    ctx.response.body = JSON.stringify(res);
});

一定要注意await的使用,await使得一个异步函数停下来等待另一个异步函数的执行结束并返回,这样就能等待query()的执行,取到数据后返回,客户端成功收到数据,运行结果:
客户端成功接收数据
至此,我们成功地使用Koa2对Mysql数据库进行操作,我们基于Node.js和Koa框架搭建一个基础的服务器,也可以说是大功告成了。但笔者对JavaScript中的Promise对象和async/await的使用还只是一个浅显的理解,异步编程的思想更需要深入学习。

参考

  1. 菜鸟教程,[Node.js 安装配置]:https://www.runoob.com/nodejs/nodejs-install-setup.html;
  2. Bootstrap前端框架,[koa]:https://koa.bootcss.com/#introduction;
  3. 廖雪峰的官方网站,[JavaScript教程/Node.js/Web开发/koa]:https://www.liaoxuefeng.com/wiki/1022910821149312/1023025933764960;
  4. CSDN软件开发网,[koa2如何允许跨域]:https://blog.csdn.net/wyw223/article/details/86745599;
  5. 博客园,[$.ajax()方法详解]:https://www.cnblogs.com/tylerdonet/p/3520862.html;
  6. 简书,[Koa2框架学习笔记]:https://www.jianshu.com/p/892d2098a75e;
  7. 简书,[nodejs查询数据库后将值返回前端]:https://www.jianshu.com/p/6e77d6fdaf13;
  8. 菜鸟教程,[JavaScript Promise 对象]:https://www.runoob.com/w3cnote/javascript-promise-object.html;
  9. CSDN软件开发网,[KOA2操作mysql数据库]:https://blog.csdn.net/weixin_34378969/article/details/91436028。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值