Some time ago, I used KOA to develop a small application. I think KOA is very simple and convenient to use, so I want to write a summary to improve my understanding of KOA.

node http

Before we look at KOA, we can look at how to build a Web service using the Node HTTP module.

const http = require("http");

const server = http.createServer((request, response) = > {
  // All HTTP requests are processed here
});

server.listen(3000);
Copy the code

The code is simple: create an HTTP service through http.createserver and listen on the port through server.Listen so that it can be accessed externally.

Http.createserver receives a function and it is executed each time an HTTP request is received. Note that this function takes two arguments, request and response, which are very important!

request

Request is an instance of IncomingMessage, which contains all information about the request, such as request methods (get, port…). Request path, request first class. In addition, the Request implements the ReadableStream interface to retrieve the contents of the body as follows:

let body = [];
request.on('data'.(chunk) = > {
  body.push(chunk);
}).on('end'.() = > {
  body = Buffer.concat(body).toString();
  // Here 'body' contains all the contents of the request body, stored in a string
});
Copy the code

response

Response is an instance of ServerResponse, through which we can set the return of the request. Status code, response, etc.

// Set the status code
response.statusCode = 404;

// Set the response header
esponse.setHeader('Content-Type'.'application/json');

Copy the code

Finally, the content is returned to the client. This depends on the WritableStream object.

response.write('Hello World');
response.end();

// Can also be simplified to
response.end('Hello World');
Copy the code

With that in mind, we can enrich the previous basic example. As follows:

const http = require('http');

http.createServer((request, response) = > {
  request.on('error'.(err) = > {  // Add an error handler
    console.error(err);
    response.statusCode = 400;
    response.end();
  });
  response.on('error'.(err) = > { // Add an error handler
    console.error(err);
  });
  // Process the request whose method is POST and path is /echo
  if (request.method === 'POST' && request.url === '/echo') {
    let body = [];
    request.on('data'.(chunk) = > {
      body.push(chunk);
    }).on('end'.() = > {
      body = Buffer.concat(body).toString();
      response.end(body); // Return all body content
    });
  } else {
    response.statusCode = 404;
    response.end();
  }
}).listen(3000);
Copy the code

The above example responds only to requests with a POST method and an access path of /echo, and returns all body content. All other requests return 404. Meanwhile, since Request and Response are Also EventEmitter objects, we can handle internal errors by listening for error events.

koa

Next up is our main character Koa. Here is the simplest KOA example, which returns a “Hello World” string for all requests.

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

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
Copy the code

We can look at the code for this example in conjunction with the previous Node HTTP example, most notably app.use, which is actually registered with KOA middleware.

middleware

Middleware is familiar, multiple middleware is sequential, they will be received after the execution of the request. Here is an example:

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

// 1. logger

app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.get('X-Response-Time');
  console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});

// 2. x-response-time

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time'.`${ms}ms`);
});

// 3. response

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
Copy the code

Here three middleware are registered in turn: log (Logger), compute request time (X-Response-time) and return (response). In a Web application, each middleware is responsible for an independent function, and finally a complete application.

Koa middleware has the following form:

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time'.`${ms}ms`);
});

Copy the code

It takes two arguments, CTX and next. CTX is the Context in KOA, which contains request and Response as described earlier, while encapsulating other useful methods (Context will be described later). Next is the next middleware, which is run by calling await Next ().

It is not hard to see that every middleware uses async and await, which is officially recommended. Why do you use it that way? This is primarily for asynchronous behavior in Node.

In the following example, we add an asynchronous Promise to the “response” middleware. What if we don’t use await in “x-Response-time”?

// 2. x-response-time

app.use(async (ctx, next) => {
  const start = Date.now();
  next(); // Do not use await!!
  const ms = Date.now() - start;
  ctx.set("X-Response-Time".`${ms}ms`);
});

// 3. response

app.use(async (ctx) => {
  const wait2seconds = () = >
    new Promise((resolve, reject) = > {
      setTimeout(() = > {
        resolve();
      }, 2000);
    });
  await wait2seconds();
  ctx.body = "Hello World";
});
Copy the code

The result is that the execution time of “x-response-time” has been calculated before the execution of “response” is completed 😫 ~

Use await to make the current function “fully run” before running subsequent logic. Therefore, in order to better control the behavior of middleware: upstream calls downstream, and after downstream execution, control returns upstream, we must use async, await to control.

But it’s not a panacea, and if you write “response” like this, you’re really out of control.

app.use(async (ctx) => {
  // No one knows when I will finish running ~
  setTimeout(() = > {
    ctx.body = "Hello World";
  }, 2000);
});
Copy the code

Middleware is simply a sequence of functions that are executed when a request comes in. Typically, a single piece of middleware does one simple thing: Body-Parser does body parsing, Logger does request logging, and so on. The ability of the upstream middleware to “control” the downstream middleware is useful in many cases, as is evident in computing time and error handling.

For KOA middleware, we should get used to async and await. This also prevents “strange” behavior in some cases, such as the return value of the middleware.

app.use(async (ctx, next) => {
  const res = next(); // Do not use await
  console.log("res", res); // What is this?
});

app.use(() = > {
  return "hello world";
});
Copy the code

The result printed in this example is not a “Hello world” string, but a Promise. This is because KOA wraps all middleware returns with a Promise layer, the source code is as follows:You can read morekoa-composeSource code is rarely friendly ~ its job is to chain all the middleware together and let them execute sequentially.

app.use

Look again at the exampleapp.useIts job is simply to stuff functions into middleware queues.

app.listen

In this case, app.listen(3000), what does it do?

listenThe source code for the method is as follows:It’s really just a simple callhttp.createServer.

Take a look atthis.callback:

App.use was previously used to push the functions into the middleware queue, where they are aggregated using compose (koa-compose). Finally, a handleRequest function is returned. Go back to the previous node HTTP example:

const http = require("http");

const server = http.createServer((request, response) = > {
  // All HTTP requests are processed here
});

server.listen(3000);
Copy the code

HandleRequest is the function that is passed into createServer. All requests are handled in this handleRequest function.

In this.handlerequest (CTX, fn), KOA passes this CTX to the middleware function FN for execution, fn(CTX). This middleware function connects all of the middleware injected through app.use, which will be executed as each request comes in, and the CTX object will exist for the entire life of the request and be used by each middleware.

ctx

The last important point is the context in KOA. It is created by createContext and passes in Node’s Request and Response objects.

CreateContext source code:

It encapsulates Node’s Request and Response objectscontextThis object also contains koArequestThe objects andresponseObject.

attribute meaning
ctx.req NoderequestObject.
ctx.res NoderesponseObject.
ctx.request The koaRequestObject.
ctx.response The koaResponseObject.
ctx.app namelyconst app = new Koa()The instance

See Koa Context for more.

conclusion

To summarize, this article examines a simple example of KOA with the Node HTTP example, exploring some of the principles in KOA.

The Node HTTP example can be represented with a graph:

The key points are threefold:

  • The request. It is an instance of IncomingMessage, which contains all the information related to the request, such as method, URL, body, and so on. Reading the contents of the body depends on the ReadableStream object.

  • The response. It is an instance of ServerResponse. It can set the return of the request, such as header, statusCode, etc. Writing content to the client depends on the WritableStream object.

  • Finally, there is a request handler function, Handle Request, which executes when all requests come in. Request and Response objects can be retrieved from this function, enabling different processing behaviors based on different requests.

Going back to KOA, we can also use a graph:

Two of the most obvious differences are the main points of KOA:

  • The Context. Koa context, which encapsulates Node’s Request and response objects and other methods for easy use in middleware.
  • Middleware. Koa’s middleware, rather than using one large processing function, the middleware system breaks the functionality into multiple middleware functions, which is logical and easy to maintain. There are many useful KOA middleware tools available in the NPM community to help us quickly build a Web application.

reference

  1. koajs
  2. Node Guide – Parsing an HTTP transport
  3. 10 minutes to understand node.js KOA source architecture design