I heard you don't know how to use Koa yet?

Introduction

KoaIs a new web framework, built by the original team Expressbehind 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. KoaIt 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, Koait is also a webframework, but it is Expresslighter and has a better asynchronous mechanism.

This article is suitable for Koastudents who have a basic foundation and urgently need to build projects. If you Koadon’t understand it at all, it is recommended to read the official Koa documentation first.

Before talking Koaabout the use, let's introduce the very famous onion model, which is very helpful for the understanding of the following code

onion model

We Expressmentioned the onion model when we introduced it earlier. As shown in the figure below, Koathe middleware execution mechanism in is also similar to an onion model, but there are Expressstill some differences.

image.png

Let's take a look Koaat how the middleware in is executed. The difference Expressis that in Koa, nextit 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 Expresssame order as .

image.png

As mentioned earlier, in Koasupport nextof asynchronous. That is await, we can add it awaitto 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

image.png

It can be seen that in Koa, awaitthe 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 nextthe first half of the code from front to back, and then execute nextthe second half of the code from back to front.

In Express, nextthe 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 Expressstill very similar.

routing

KoaExpressThere is still a difference between the routing and the . KoaThe ones appthat 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/xxxcall the interface through.

Automatically register routes

Similarly, if there are many modules, we can also optimize, fsread 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/模块路由前缀/xxxcall 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、bodythree forms

query parameter

For queryparameters, req.queryget by

router.get("/", (ctx) => {
    
    
  const query = ctx.query;
  // const query = ctx.request.query; // 上面是简写形式
  ctx.body = query;
});

The parameters can be obtained normally

image.png

Let's look at the path parameters again

path parameters

For path parameters, pass the :variable definition, and then pass request.paramsthe 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

image.png

body parameter

For bodyparameters, that is, the parameters in the request body, you need to use koa-bodyplug-ins.

First install koa-bodythe plugin

npm i koa-body

Then in the entry file use

const {
    
     koaBody } = require("koa-body");

app.use(koaBody());

Then ctx.request.bodyyou 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.

image.png

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. ExpressMuch 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 Expressis that whether it is a single file or multiple files, it is obtained by ctx.request.filesobtaining 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 keyis empty.

image.png

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 keyis our form field name.

image.png

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.

image.png

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 keyempty, and if there are multiple files, it is returned in the form of an array.

image.png

image.png

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, koayou need koa-statica plug-in to open the static directory.

The following configuration is to uploadsset 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.

image.png

Careful students may have discovered that it is accessed directly behind the domain name, and does not Expresshave a staticprefix 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-mountthe plug-in

Let's install it first

npm i koa-mount

Then koa-staticuse it with

app.use(mount("/static", koaStatic(path.join(__dirname, "uploads"))));

Then we can /staticaccess static resources with the prefix.

image.png

error handling

koaIt 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.

image.png

Let's look at async errors again

router.get("/error2", async function (ctx, next) {
    
    
  // 新建异步错误
  await Promise.reject(new Error("异步错误"));
});

can also be captured normally.

image.png

It can be seen that compared to Expressthe error handling, Koait 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 Expressused in conjunction with it. This article briefly explains Expresshow to use log4js

Let's install the plugin first. The version I installed here is6.8.0

npm install log4js

Then we create a utilsfolder log.jsto 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

image.png

log level

Let's change the level of the output log again

logger.level = "warn"; // 需要打印的日志等级

warnLet’s test it again, and find that only the logs of and level are output error, debugand infothe logs of and level are filtered out.

image.png

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.logthe file and writes the log into it.

image.png

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 dba folder, and then create mongodb.js, here to connect to our mongodbdatabase

// 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 mongodbthe connection is successful.

image.png

We look at mongodbthe panel and we can see that KoaApithe database has also been created successfully

image.png

The database connection is successful, now we will formally create the interface.

We use mvcmode to create model、controller、routethree 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

  1. create model
  2. create controller
  3. create route
  4. 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 userthe 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

image.png

Let's take a look at the database and find that usera new record has been added to the table.

image.png

Let's take a look at the query interface again, and the data can also be returned normally.

image.png

So far, our mongodbinterface has been created and tested successfully.

connect to mysql

In order to simplify our operations, here we use ORMthe framework sequelize .

Let's install these two libraries first

npm i mysql2 sequelize

Then dbcreate a directory mysql.jsfor 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 koaapicreated in advance. It is not created automatically.

As before, creating an interface is divided into four steps

  1. create model
  2. create controller
  3. create route
  4. 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

image.png

Let's take a look at the database and find that usersa new record has been added to the table.

image.png

Let's take a look at the query interface again, and the data can also be returned normally.

image.png

So far, our mysqlinterface 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 dbcreate a directory redis.jsfor 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

image.png

Let's check our redisdatabase again and find that the data is saved successfully.

image.png

Of course, this is just a simple introduction, and redisthere are many more operations. You can read the official documents, and the author will not go into details here.

token verification

For tokenauthentication, 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 tokenand 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: "账号或密码错误",
    };
  }
}

// ...

tokenWe 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

image.png

Then we try to enter the correct account password, and we can see that tokenit is returned normally.

image.png

At this point we have no problem passing jsonwebtokenthe generation . tokenNext is how to verify token.

token decryption

Before we talk about tokenverification, let's talk about it token解密. Generally speaking, tokendecryption 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 JWTone can be decoded.

Let's test it out,

Install the plugin first

npm i jwt-decode

jwt-decodeThen 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 tokenbe parsed correctly.

image.png

This plug-in is generally used more in our front end, for example, if we want to analyze tokenand see what data is inside. It does not verify tokenexpiration. If you want to verify token, you have to use the following method.

token verification

In Koa, tokenwe 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 tokenare valid.

Then remember to use it in conjunction with error middleware.

If some interfaces do not want to be verified, you can use unlessexclusions, 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 tokenthe unnecessary interface to access a static resource. It can be seen that tokenthe resource cannot be obtained normally.

image.png

Let's visit a required tokeninterface again, and we can see that it prompts an error, saying that there is notoken

image.png

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.

image.png

Because our tokensettings are valid for one minute, we will request the interface after one minute. As you can see, it prompts tokenan error.

image.png

Alright, tokenthat's all for verification.

start up

In node, generally we will use node xx.jsto run a jsfile. 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 Nodeprocess management tool, which can be used to simplify many Nodetedious 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

  1. Start the application:pm2 start xxx.js
  2. View all processes:pm2 list
  3. Stop a process:pm2 stop name/id
  4. Stop all processes:pm2 stop all
  5. Restart a process:pm2 restart name/id
  6. 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.

image.png

Of course, pm2the use of it is far more than that. You can check the PM2 documentation to learn by yourself.

Summarize

Generally speaking, koait 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 awaitwill 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!

Guess you like

Origin blog.csdn.net/weixin_38664300/article/details/131008313