Introduction
Koa
Is a new web framework, built by the original team Express
behind the scenes , committed to becoming a smaller, more expressive, and more robust cornerstone in the field of web application and API development. By taking advantage of async functions, Koa helps you ditch callbacks and greatly enhances error handling. Koa
It does not bundle any middleware, but provides a set of elegant methods to help you write server-side applications quickly and happily.
In simple terms, Koa
it is also a web
framework, but it is Express
lighter and has a better asynchronous mechanism.
This article is suitable for Koa
students who have a basic foundation and urgently need to build projects. If you Koa
don’t understand it at all, it is recommended to read the official Koa documentation first.
Before talking Koa
about the use, let's introduce the very famous onion model, which is very helpful for the understanding of the following code
onion model
We Express
mentioned the onion model when we introduced it earlier. As shown in the figure below, Koa
the middleware execution mechanism in is also similar to an onion model, but there are Express
still some differences.
Let's take a look Koa
at how the middleware in is executed. The difference Express
is that in Koa
, next
it supports asynchronous.
// 全局中间件
app.use(async (ctx, next) => {
console.log(
"start In comes a " + ctx.request.method + " to " + ctx.request.url
);
next();
console.log(
"end In comes a " + ctx.request.method + " to " + ctx.request.url
);
});
// 单独中间件
router.get(
"/select",
async (ctx, next) => {
console.log("我是单独中间件 start");
const result = await Promise.resolve(123);
console.log(result);
next();
console.log("我是单独中间件 end");
},
async (ctx) => {
console.log("send result start");
ctx.body = "get method!";
console.log("send result end");
}
);
Looking at the output above, you can see that it executes in the Express
same order as .
As mentioned earlier, in Koa
support next
of asynchronous. That is await
, we can add it await
to test
// 全局中间件
app.use(async (ctx, next) => {
console.log(
"start In comes a " + ctx.request.method + " to " + ctx.request.url
);
await next();
console.log(
"end In comes a " + ctx.request.method + " to " + ctx.request.url
);
});
// 单独中间件
router.get(
"/select",
async (ctx, next) => {
console.log("我是单独中间件 start");
const result = await Promise.resolve(123);
console.log(result);
await next();
console.log("我是单独中间件 end");
},
async (ctx) => {
console.log("send result start");
ctx.body = "get method!";
console.log("send result end");
}
);
look at the results
It can be seen that in Koa
, await
the execution of all subsequent codes will be blocked, which fully guarantees the execution of codes according to the onion model. Think ofnext
the watershed, first execute next
the first half of the code from front to back, and then execute next
the second half of the code from back to front.
In Express
, next
the method does not support asynchrony await
, which is the biggest difference from the onion model Koa
.Express
create application
First we need to installkoa
npm i koa
Then import using
const Koa = require("koa");
const app = new Koa();
app.listen(3000, () => {
console.log("serve running on 3000");
});
This is Express
still very similar.
routing
Koa
Express
There is still a difference between the routing and the . Koa
The ones app
that do not support direct routing need to use third-party plug-ins koa-router
.
Let's install first
npm i @koa/router
Then you can import and use
// routes/user.js
const Router = require("@koa/router");
const router = new Router({
prefix: "/user" }); // 路由前缀
router.get("/select", (ctx) => {
ctx.body = "get";
});
router.post("/add", (ctx) => {
ctx.body = "post";
});
router.delete("/delete", (ctx) => {
ctx.body = "delete";
});
router.put("/update", (ctx) => {
ctx.body = "put";
});
// 所有请求都支持
router.all("/userall", (ctx) => {
ctx.body = "所有请求都可以?" + ctx.method;
});
// 重定向
router.get("/testredirect", (ctx) => {
ctx.redirect("/user/select");
});
module.exports = router;
Then in the entry file, import the route and register it to use
const Koa = require("koa");
const app = new Koa();
const userRouter = require("./routes/user");
app.use(userRouter.routes()).use(userRouter.allowedMethods());
In this way, we can localhost:3000/user/xxx
call the interface through.
Automatically register routes
Similarly, if there are many modules, we can also optimize, fs
read the file through the module, and automatically complete the registration of the route.
// routes/index.js
const fs = require("fs");
// 批量注册路由
module.exports = (app) => {
fs.readdirSync(__dirname).forEach((file) => {
if (file === "index.js") {
return;
}
const route = require(`./${
file}`);
app.use(route.routes()).use(route.allowedMethods());
});
};
In the entry file, we can register routes in batches through this method
const registerRoute = require("./routes/index");
registerRoute(app);
In this way, we can localhost:3000/模块路由前缀/xxx
call the interface through.
Now that the routing is finished, let’s take a look at how to get the parameters
parameter acquisition
The acquisition of parameters is divided into query、param、body
three forms
query parameter
For query
parameters, req.query
get by
router.get("/", (ctx) => {
const query = ctx.query;
// const query = ctx.request.query; // 上面是简写形式
ctx.body = query;
});
The parameters can be obtained normally
Let's look at the path parameters again
path parameters
For path parameters, pass the :variable definition, and then pass request.params
the get.
router.get("/user2/:name/:age", (ctx) => {
// 路径参数获取
const params = ctx.params;
// const params = ctx.request.params; // 上面是简写形式
ctx.body = params
});
The parameters can be obtained normally
body parameter
For body
parameters, that is, the parameters in the request body, you need to use koa-body
plug-ins.
First install koa-body
the plugin
npm i koa-body
Then in the entry file use
const {
koaBody } = require("koa-body");
app.use(koaBody());
Then ctx.request.body
you can get the parameters through it.
router.post("/", (ctx) => {
const body = ctx.request.body;
ctx.body = body;
});
After setting, let's test it, and the parameters are obtained normally.
File Upload
After talking about the acquisition of parameters, let's take a look at how to handle file uploads.
In koa
, the plug-in is also used for file uploading koa-body
, and only the parameters of uploading files need to be configured in it. Express
Much simpler than that .
app.use(
koaBody({
// 处理文件上传
multipart: true,
formidable: {
// 使用oss上传就注释 上传到本地就打开。路径必须事先存在
uploadDir: path.join(__dirname, "./uploads"),
keepExtensions: true,
},
})
);
After configuration, let's test it
The difference Express
is that whether it is a single file or multiple files, it is obtained by ctx.request.files
obtaining the file.
single file upload
router.post("/file", (ctx) => {
const files = ctx.request.files;
ctx.body = files;
});
As we can see, it returns an object, and when the form fields are not filled, it key
is empty.
Let's look at the form fields again
router.post("/file2", (ctx) => {
const files = ctx.request.files;
ctx.body = files;
});
As you can see, the object it returns key
is our form field name.
Let's look at the case of multiple file uploads
Multiple file upload
Let's take a look at the case of multiple files without form fields
router.post("/files", (ctx) => {
const files = ctx.request.files;
ctx.body = files;
});
As you can see, it returns an object, but the attribute value is an array.
Let's take a look at the case with form fields. For multiple file uploads with form fields, the value in the returned object is not key
empty, and if there are multiple files, it is returned in the form of an array.
static directory
We have finished the introduction of file upload, what should we do if we want to access the pictures we uploaded? Can you access it directly?
For files, we need to enable the static directory to access the contents of our directory through links. Unlike Express
, koa
you need koa-static
a plug-in to open the static directory.
The following configuration is to uploads
set the directory of our system as a static directory, so that we can directly access the contents of this directory through the domain name.
const koaStatic = require("koa-static");
app.use(koaStatic(path.join(__dirname, "uploads")));
As you can see, the pictures can be accessed correctly.
Careful students may have discovered that it is accessed directly behind the domain name, and does not Express
have a static
prefix like the same. So how to achieve the effect of this custom prefix?
Custom static resource directory prefix
In Koa
, you need to use koa-mount
the plug-in
Let's install it first
npm i koa-mount
Then koa-static
use it with
app.use(mount("/static", koaStatic(path.join(__dirname, "uploads"))));
Then we can /static
access static resources with the prefix.
error handling
koa
It is also possible to catch errors through middleware, but it should be noted that this middleware needs to be written in front .
app.use(async (ctx, next) => {
try {
await next();
} catch (error) {
// 响应用户
ctx.status = error.status || 500;
ctx.body = error.message || "服务端错误";
// ctx.app.emit("error", error); // 触发应用层级error事件
}
});
let's test it
// 模拟错误
router.get("/error", function (ctx, next) {
// 同步错误可以直接捕获
throw new Error("同步错误");
});
As you can see, the error was caught by the middleware and returned normally.
Let's look at async errors again
router.get("/error2", async function (ctx, next) {
// 新建异步错误
await Promise.reject(new Error("异步错误"));
});
can also be captured normally.
It can be seen that compared to Express
the error handling, Koa
it has become simpler, and both synchronous and asynchronous errors can be captured normally.
log
For online projects, the log is a very important part. log4js is a log component that is used more often, and is often Express
used in conjunction with it. This article briefly explains Express
how to use log4js
Let's install the plugin first. The version I installed here is6.8.0
npm install log4js
Then we create a utils
folder log.js
to create one logger
.
// utils/log.js
const log4js = require("log4js");
const logger = log4js.getLogger();
logger.level = "debug"; // 需要打印的日志等级
module.exports = logger;
Just import it where you need it logger
, let's test it
app.get("/logtest", (req, res) => {
logger.debug("Some debug messages");
logger.info("Some info messages");
logger.warn("Some warn messages");
logger.error("Some error messages");
res.send("test log");
});
As you can see, the logs are all printed
log level
Let's change the level of the output log again
logger.level = "warn"; // 需要打印的日志等级
warn
Let’s test it again, and find that only the logs of and level are output error
, debug
and info
the logs of and level are filtered out.
log output to file
If the log wants to be output to a file, we can also configurelog4js
const log4js = require("log4js");
log4js.configure({
appenders: {
test: {
type: "file", filename: "applog.log" } },
categories: {
default: {
appenders: ["test"], level: "warn" } },
});
const logger = log4js.getLogger();
module.exports = logger;
Let's test it again and find that it automatically creates applog.log
the file and writes the log into it.
Connect to the database
Databases currently mainly include relational databases, non-relational databases, and cache databases. Let us give an example for each of these three types of databases.
connect mongodb
For the convenience of operation mongodb
, we use the mongoose plugin .
First let's install
npm i mongoose
After installation, we first create db
a folder, and then create mongodb.js
, here to connect to our mongodb
database
// db/mongodb.js
const mongoose = require("mongoose");
module.exports = () => {
// 数据库连接
return new Promise((resolve, reject) => {
mongoose
.connect("mongodb://localhost/ExpressApi", {
// useNewUrlParser: true,
// useUnifiedTopology: true,
// useFindAndModify: false,
})
.then(() => {
console.log("mongodb数据库连接成功");
resolve();
})
.catch((e) => {
console.log(e);
console.log("mongodb数据库连接失败");
reject();
});
});
};
Then in our entry file reference use
// index.js
// 连接mongodb
const runmongodb = require("./db/mongodb.js");
runmongodb();
Save it, let's run it, and you can see that mongodb
the connection is successful.
We look at mongodb
the panel and we can see that KoaApi
the database has also been created successfully
The database connection is successful, now we will formally create the interface.
We use mvc
mode to create model、controller、route
three folders to manage models, controllers, and routes respectively.
The overall project directory is as follows
model // 模型
controller // 控制器
route // 路由
db // 数据库连接
index.js // 入口文件
Creating an interface is divided into four steps in total
- create model
- create controller
- create route
- use routing
Let's first create auser model
// model/user.js
const mongoose = require("mongoose");
// 建立用户表
const UserSchema = new mongoose.Schema(
{
username: {
type: String,
unique: true,
},
password: {
type: String,
select: false,
},
},
{
timestamps: true }
);
// 建立用户数据库模型
module.exports = mongoose.model("User", UserSchema);
Then create user
the controller, defining a save and a query method.
// controller/userController.js
const User = require("../model/user");
class UserController {
async create(ctx) {
const {
username, password } = ctx.request.body;
const repeatedUser = await User.findOne({
username, password });
if (repeatedUser) {
ctx.status = 409;
ctx.body = {
message: "用户已存在",
};
} else {
const user = await new User({
username, password }).save();
ctx.body = user;
}
}
async query(ctx) {
const users = await User.find();
ctx.body = users;
}
}
module.exports = new UserController();
Then we define the query and create interface in the route
// route/user.js
const Router = require("@koa/router");
const router = new Router({
prefix: "/user" });
const {
create, query } = require("../controller/userController");
router.post("/create", create);
router.get("/query", query);
module.exports = router;
Finally, we use this route in the entry file. As we said earlier, if there are few routes, you can import them one by one. If there are many routes, it is recommended to use automatic injection.
For the convenience of understanding, here we still use the import method
// index.js
const userRouter = require("./routes/user");
app.use(userRouter.routes()).use(userRouter.allowedMethods())
Well, through these four steps, our interface is defined, let's test it
Let’s take a look at the new additions first, the interface returns normally
Let's take a look at the database and find that user
a new record has been added to the table.
Let's take a look at the query interface again, and the data can also be returned normally.
So far, our mongodb
interface has been created and tested successfully.
connect to mysql
In order to simplify our operations, here we use ORM
the framework sequelize .
Let's install these two libraries first
npm i mysql2 sequelize
Then db
create a directory mysql.js
for connection mysql
.
const Sequelize = require("sequelize");
const sequelize = new Sequelize("KoaApi", "root", "123456", {
host: "localhost",
dialect: "mysql",
});
// 测试数据库链接
sequelize
.authenticate()
.then(() => {
console.log("数据库连接成功");
})
.catch((err) => {
// 数据库连接失败时打印输出
console.error(err);
throw err;
});
module.exports = sequelize;
It should be noted here that the database needs to be koaapi
created in advance. It is not created automatically.
As before, creating an interface is divided into four steps
- create model
- create controller
- create route
- use routing
First we create model
, here we createuser2.js
// model/user2.js
const Sequelize = require("sequelize");
const sequelize = require("../db/mysql");
const User2 = sequelize.define("user", {
username: {
type: Sequelize.STRING,
},
password: {
type: Sequelize.STRING,
},
});
//同步数据库:没有表就新建,有就不变
User2.sync();
module.exports = User2;
Then create the controller, defining a save and a query method.
// controller/user2Controller.js
const User2 = require("../model/user2.js");
class user2Controller {
async create(ctx) {
const {
username, password } = ctx.request.body;
try {
const user = await User2.create({
username, password });
ctx.body = user;
} catch (error) {
ctx.status = 500;
ctx.body = {
code: 0, message: "保存失败" };
}
}
async query(ctx) {
const users = await User2.findAll();
ctx.body = users;
}
}
module.exports = new user2Controller();
Then define two routes
const router = new Router({ prefix: "/user2" });
const { query, create } = require("../controller/user2Controller");
// 获取用户
router.get("/query", query);
// 添加用户
router.post("/create", create);
module.exports = router;
Finally use this route in the entry file
// index.js
const user2Router = require("./routes/user2");
app.use(user2Router.routes()).use(user2Router.allowedMethods())
Well, through these four steps, our interface is defined, let's test it
Let’s take a look at the new additions first, the interface returns normally
Let's take a look at the database and find that users
a new record has been added to the table.
Let's take a look at the query interface again, and the data can also be returned normally.
So far, our mysql
interface has been created and tested successfully.
Let's look at the cache database again redis
.
connect redis
Here we also need to use the node-redis plugin
Let's install first
npm i redis
Then db
create a directory redis.js
for connectionredis
// db/redis.js
const {
createClient } = require("redis");
const client = createClient();
// 开启连接
client.connect();
// 连接成功事件
client.on("connect", () => console.log("Redis Client Connect Success"));
// 错误事件
client.on("error", (err) => console.log("Redis Client Error", err));
module.exports = client;
Then we create a simple route to test
// route/dbtest
const Router = require("@koa/router");
const router = new Router({
prefix: "/dbtest" });
const client = require("../db/redis");
router.get("/redis", async (ctx) => {
await client.set("name", "randy");
const name = await client.get("name");
ctx.body = {
name };
});
module.exports = router;
Then register the route in the entry file for use
// index.js
const dbtestRouter = require("./routes/dbtest");
app.use(dbtestRouter.routes()).use(dbtestRouter.allowedMethods())
Finally, let's test the interface, and we can see that the interface returns normally
Let's check our redis
database again and find that the data is saved successfully.
Of course, this is just a simple introduction, and redis
there are many more operations. You can read the official documents, and the author will not go into details here.
token verification
For token
authentication, we use the currently popular scheme jsonwebtoken here .
generate token
We start by installing jsonwebtoken .
npm i jsonwebtoken
After installation, let's implement a login interface, generate it in the interface token
and return it to the front end.
Note that because it is a demonstration, the key is hard-coded, and the real project is best obtained dynamically from the environment variable.
// route/user.js
const jwt = require("jsonwebtoken");
// ...
async login(ctx) {
const {
username, password } = ctx.request.body;
const user = await User.findOne({
username, password });
if (user) {
const token = jwt.sign(
{
id: user.id, username: user.username },
"miyao",
{
expiresIn: 60 }
);
ctx.body = {
token,
};
} else {
ctx.status = 401;
ctx.body = {
message: "账号或密码错误",
};
}
}
// ...
token
We have defined the interface generated here , let's test it.
First enter the wrong account number, and see that it prompts that the account password is wrong
Then we try to enter the correct account password, and we can see that token
it is returned normally.
At this point we have no problem passing jsonwebtoken
the generation . token
Next is how to verify token
.
token decryption
Before we talk about token
verification, let's talk about it token解密
. Generally speaking, token
decryption is not required. But if you have to see what is inside and there is a way to decrypt it, then you have to use the jwt-decode plug-in.
The plugin does not validate keys, any well-formed JWT
one can be decoded.
Let's test it out,
Install the plugin first
npm i jwt-decode
jwt-decode
Then use parsing in the login interfacetoken
const decoded = require("jwt-decode");
async login(req, res) {
// ...
console.log("decoded token", decoded(token));
// ...
}
It can be seen that even if there is no secret key, our password can token
be parsed correctly.
This plug-in is generally used more in our front end, for example, if we want to analyze token
and see what data is inside. It does not verify token
expiration. If you want to verify token
, you have to use the following method.
token verification
In Koa
, token
we generally choose the koa-jwt plug-in to verify whether it is valid.
The following author will demonstrate how to use
First still install
npm install koa-jwt
Then use it as a global middleware in the entry file.
We want to put this middleware in the front as much as possible, because we need to verify that all interfaces token
are valid.
Then remember to use it in conjunction with error middleware.
If some interfaces do not want to be verified, you can use unless
exclusions, such as login interfaces and static resources.
// index.js
const koaJwt = require("koa-jwt");
app.use(
koaJwt({
secret: "miyao" }).unless({
path: [/^\/user\/login/, "/static"] })
);
// 错误中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (error) {
// 响应用户
ctx.status = error.status || 500;
ctx.body = error.message || "服务端错误";
// ctx.app.emit("error", error); // 触发应用层级error事件
}
});
Let's test it out,
Let's first take a look at token
the unnecessary interface to access a static resource. It can be seen that token
the resource cannot be obtained normally.
Let's visit a required token
interface again, and we can see that it prompts an error, saying that there is notoken
We use the login interface to generate one token
, and then add it to the interface for testing. We can see that the interface has obtained data normally.
Because our token
settings are valid for one minute, we will request the interface after one minute. As you can see, it prompts token
an error.
Alright, token
that's all for verification.
start up
In node
, generally we will use node xx.js
to run a js
file. This method not only cannot run in the background, but if an error is reported, it may stop directly and cause the entire service to crash.
PM2 is a Node
process management tool, which can be used to simplify many Node
tedious tasks of application management, such as performance monitoring, automatic restart, load balancing, etc., and it is very easy to use.
First we need to install globally
npm i pm2 -g
Let's briefly talk about some of its basic commands
- Start the application:
pm2 start xxx.js
- View all processes:
pm2 list
- Stop a process:
pm2 stop name/id
- Stop all processes:
pm2 stop all
- Restart a process:
pm2 restart name/id
- Delete a process:
pm2 delete name/id
For example, if we start the current application here, we can see that it starts the application in the background mode.
Of course, pm2
the use of it is far more than that. You can check the PM2 documentation to learn by yourself.
Summarize
Generally speaking, koa
it is lighter, and many functions are not built-in but need to be installed separately. And it has better support for asynchrony, that is, it await
will block the execution of the following code (including middleware).
series of articles
Introduction to Node.js What is Node.js
The path module for getting started with Node.js
The fs module for getting started with Node.js
The url module and querystring module for getting started with Node.js
http module and dns module for getting started with Node.js
Node.js entry process module, child_process module, cluster module
I heard you don't know how to use Express yet
I heard you don't know how to use Koa yet?
postscript
Thank you friends for watching patiently. This article is the author's personal study notes. If there is any fallacy, please let me know. Thank you very much! If this article is helpful to you, please follow and like~, your support is the motivation for the author to keep updating!