Login With Github

The Detailed Starter Tutorial For Koa Framework

This is an article from the famous Chinese personal blog ruanyifeng. A developer from China translated and submitted this article to us. We felt that the article was well written, so we published the translation on the website and shared it with everyone. The following is the content of the article:

Node is primarily used to develop web applications. So Node often should be used with Web application frameworks.

Koa is a simple web framework and easy to use. It is characterized by its elegance, simplicity, expressiveness and high freedom. There are only more than 1000 lines for the code itself, and all functions are implemented through plug-ins, which is on the ground of the philosophy of Unix.

This article starts from scratch, and teaches you how to use Koa to write your own web application step by step. There will be a simple and easy-to-understand example for each step, so let's start.

0. Preparation

First, check the Node version.

$ node -v
v8.0.0

You must use a version above 7.6 for Koa. If your version is lower than that, you must upgrade Node first.

Then, clone the companion sample library for this article. (If you don't want to use Git, you can also download the zip file to extract it.)

$ git clone https://github.com/ruanyf/koa-demos.git

Next, go into the sample library and install the dependencies.

$ cd koa-demos
$ npm install

All sample source code is under the demos directory.

1. Basic Usage

1.1 Set up HTTP services

You can set up an HTTP service with Koa by just three lines of code.

// demos/01.js
const Koa = require('koa');
const app = new Koa();

app.listen(3000);

Run the script.

$ node demos/01.js

Open a browser and go to http://127.0.0.1:3000. You will see the page display "Not Found", indicating that nothing has been found. This is because we didn't tell Koa what to show.

1.2 Context object

Koa provides a Context object which represents the context of a conversation (including the HTTP request and HTTP response). You can control what is returned to the user by processing the object.

The Context.response.body property is used for the content sent to the user. Let's take a look at the example below (see the full code here).

// demos/02.js
const Koa = require('koa');
const app = new Koa();

const main = ctx => {
  ctx.response.body = 'Hello World';
};

app.use(main);
app.listen(3000);

In the above code, the main function is used to set ctx.response.body. Then, use the app.use method to load the main function.

As you may have guessed, ctx.response represents the HTTP Response. Similarly, ctx.request represents the HTTP Request.

Run the demo.

$ node demos/02.js

Visit http://127.0.0.1:3000 and you will see "Hello World" now.

1.3 Types of HTTP Response

The default return type of Koa is text/plain. If you want to return other types, you can first use ctx.request.accepts to determine what data the client wants to accept (according to the Accept field of HTTP Request) and then use ctx.response.type to specify the return type. Let's take a look at the example below (see the full code here).

// demos/03.js
const main = ctx => {
  if (ctx.request.accepts('xml')) {
    ctx.response.type = 'xml';
    ctx.response.body = '<data>Hello World</data>';
  } else if (ctx.request.accepts('json')) {
    ctx.response.type = 'json';
    ctx.response.body = { data: 'Hello World' };
  } else if (ctx.request.accepts('html')) {
    ctx.response.type = 'html';
    ctx.response.body = '<p>Hello World</p>';
  } else {
    ctx.response.type = 'text';
    ctx.response.body = 'Hello World';
  }
};

Run the demo.

$ node demos/03.js

Visit http://127.0.0.1:3000 and you'll see an XML document now.

1.4 Webpage templates

In actual development, webpages returned to users are often written as template files. We can have Koa read the template file first and then return the template to the user. Take a look at the example below (see the full code here).

// demos/04.js
const fs = require('fs');

const main = ctx => {
  ctx.response.type = 'html';
  ctx.response.body = fs.createReadStream('./demos/template.html');
};

Run the Demo.

$ node demos/04.js

Visit http://127.0.0.1:3000 and what will be seen are the contents of the template file.

2. Routing

2.1 Native routing

Generally, there are multiple pages for a website. The path requested by the user can be obtained through ctx.request.path, thereby implementing simple routing. Let's take a look at the example below (see the full code here).

// demos/05.js
const main = ctx => {
  if (ctx.request.path !== '/') {
    ctx.response.type = 'html';
    ctx.response.body = '<a href="/">Index Page</a>';
  } else {
    ctx.response.body = 'Hello World';
  }
};

Run the demo.

$ node demos/05.js

Visit http://127.0.0.1:3000/about and you'll see a link. It will jump to the home page if you click on the link.

2.2 koa-route module

Native routing is not very convenient to use, so we can use the encapsulated koa-route module. Let's take a look at the example below (see the full code here).

// demos/06.js
const route = require('koa-route');

const about = ctx => {
  ctx.response.type = 'html';
  ctx.response.body = '<a href="/">Index Page</a>';
};

const main = ctx => {
  ctx.response.body = 'Hello World';
};

app.use(route.get('/', main));
app.use(route.get('/about', about));

In the above code, the handler of the root path / is main, the handler of the /about path is about.

Run the demo.

$ node demos/06.js

Visit http://127.0.0.1:3000/about and you'll find the effect is exactly the same as the previous example.

2.3 Static Resources

If the site provides static resources (images, fonts, style sheets, scripts, etc.), it will be cumbersome to write routes for them one by one, and it's unnecessary to do so. The koa-static module encapsulates this part of the request. Let's take a look at the example below (see the full code here).

// demos/12.js
const path = require('path');
const serve = require('koa-static');

const main = serve(path.join(__dirname));
app.use(main);

Run the Demo.

$ node demos/12.js

Visit http://127.0.0.1:3000/12.js and you will see the contents of the script in your browser.

2.4 Redirection

The server needs to redirect requests in some situations. For example, you need to redirect the user to the page before the login after he logs in. The ctx.response.redirect() method can issue a 302 jump to direct the user to another route. Let's take a look at the example below (see the full code here).

// demos/13.js
const redirect = ctx => {
  ctx.response.redirect('/');
  ctx.response.body = '<a href="/">Index Page</a>';
};

app.use(route.get('/redirect', redirect));

Run the demo.

$ node demos/13.js

Visit http://127.0.0.1:3000/redirect and the browser will direct the user to the root route.

3. Middlewares

3.1 The Logger functionality

The biggest feature and the most important design of Koa is Middleware. To understand the middleware, let's take a look at the implementation of the Logger functionality.

The easiest way to write is to add a line into the main function (see the full code here).

// demos/07.js
const main = ctx => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
  ctx.response.body = 'Hello World';
};

Run the Demo.

$ node demos/07.js

Visit http://127.0.0.1:3000 and the command line will output the log.

1502144902843 GET /

3.2 The concept of middleware

The Logger functionality in the previous example can be split into an independent function (see the full code here).

// demos/08.js
const logger = (ctx, next) => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
  next();
}
app.use(logger);

The logger function in the above code is called "middleware", because it is between the HTTP Request and HTTP Response and is used to implement some intermediate functionality. app.use() is used to load middlewares.

Basically, all functionalities of Koa can be implemented through middlewares, and the main in the previous example is also a middleware. Each middleware accepts two parameters by default. The first parameter is the Context object and the second is the next function. As long as the next function is called, the execution will be transferred to the next middleware.

Run the demo.

$ node demos/08.js

Visit http://127.0.0.1:3000 and the command line window will display the same log output as the previous example.

3.3 The middle stack

Multiple middlewares form a middle stack, which is executed in the order of "first-in-last-out".

  1. The outermost middleware will be executed first.
  2. Call the next function and pass the execution right to the next middleware.
  3. ...
  4. The innermost middleware will be executed last.
  5. After the execution is finished, the execution right will be returned to the middleware of the upper layer.
  6. ...
  7. After the outermost middleware takes back the execution right, execute the code following the next function.

Let's take a look at the example below (see the full code here).

// demos/09.js
const one = (ctx, next) => {
  console.log('>> one');
  next();
  console.log('<< one');
}

const two = (ctx, next) => {
  console.log('>> two');
  next(); 
  console.log('<< two');
}

const three = (ctx, next) => {
  console.log('>> three');
  next();
  console.log('<< three');
}

app.use(one);
app.use(two);
app.use(three);

Run the demo.

$ node demos/09.js

Visit http://127.0.0.1:3000 and you'll see the following output in the command line window.

>> one
>> two
>> three
<< three
<< two
<< one

If the next function is not called inside the middleware, the execution right will not be passed. As an exercise, you can comment out the line where the next() is located in the two function to see what will happen.

3.4 Asynchronous middlewares

So far, the middlewares for all the examples have been synchronous and have not contained asynchronous operations. If there is an asynchronous operation (such as reading a database), the middleware must be written as an async function. Let's take a look at the example below (see the full code here).

// demos/10.js
const fs = require('fs.promised');
const Koa = require('koa');
const app = new Koa();

const main = async function (ctx, next) {
  ctx.response.type = 'html';
  ctx.response.body = await fs.readFile('./demos/template.html', 'utf8');
};

app.use(main);
app.listen(3000);

In the above code, fs.readFile is an asynchronous operation, so it must be written as await fs.readFile(), and the middleware must be written as an async function.

Run the demo.

$ node demos/10.js

Visit http://127.0.0.1:3000 and you'll see the contents of the template file.

3.5 Composition of middlewares

The koa-compose module can compose multiple middlewares into one. Let's take a look at the example below (see the full code here).

// demos/11.js
const compose = require('koa-compose');

const logger = (ctx, next) => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
  next();
}

const main = ctx => {
  ctx.response.body = 'Hello World';
};

const middlewares = compose([logger, main]);
app.use(middlewares);

Run the demo.

$ node demos/11.js

Visit http://127.0.0.1:3000 and you'll see the log information in the command line window.

4. Error Handling

4.1 500 error

If an error occurs while the code is running, we need to return the error message to the user. The HTTP protocol promises to return a 500 status code in that case. Koa provides the ctx.throw() method to throw an error, and ctx.throw(500) is used to throw a 500 error. Let's take a look at the example below (see the full code here).

// demos/14.js
const main = ctx => {
  ctx.throw(500);
};

Run the demo.

$ node demos/14.js

Visit http://127.0.0.1:3000 and you will see a 500 error page "Internal Server Error".

4.2 404 error

If ctx.response.status is set to 404, it is equivalent to ctx.throw(404), which will return a 404 error. Let's take a look at the example below (see the full code here).

// demos/15.js
const main = ctx => {
  ctx.response.status = 404;
  ctx.response.body = 'Page Not Found';
};

Run the demo.

$ node demos/15.js

Visit http://127.0.0.1:3000 and you will see a 404 page "Page Not Found".

4.3 The middleware for error handling

To facilitate handling errors, it's best to capture them using try...catch. However, it's too much trouble to write try...catch for each middleware, so we can make the outermost middleware responsible for the error handling of all middlewares. Let's take a look at the example below (see the full code here).

// demos/16.js
const handler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.response.status = err.statusCode || err.status || 500;
    ctx.response.body = {
      message: err.message
    };
  }
};

const main = ctx => {
  ctx.throw(500);
};

app.use(handler);
app.use(main);

Run the demo.

$ node demos/16.js

Visit http://127.0.0.1:3000 and you will see a 500 page with an error message {"message":"Internal Server Error"}.

4.4 Listen for error events

Koa will trigger the error event if an error occurs during the run. You can also handle the error by listening for the event. Let's take a look at the example below (see the full code here).

// demos/17.js
const main = ctx => {
  ctx.throw(500);
};

app.on('error', (err, ctx) =>
  console.error('server error', err);
);

Run the demo.

$ node demos/17.js

Visit http://127.0.0.1:3000 and you will see "server error xxx" in the command line window.

4.5 Release the error event

It should be noted that if the error is caught by try...catch, the error event will not be triggered. Thus you must call ctx.app.emit() to release the error event manually in order to make the listener works. Let's take a look at the example below (see the full code here).

// demos/18.js`
const handler = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.response.status = err.statusCode || err.status || 500;
    ctx.response.type = 'html';
    ctx.response.body = '<p>Something wrong, please contact administrator.</p>';
    ctx.app.emit('error', err, ctx);
  }
};

const main = ctx => {
  ctx.throw(500);
};

app.on('error', function(err) {
  console.log('logging error ', err.message);
  console.log(err);
});

In the above code, the error thrown by the main function is caught by the handler function. You can use ctx.app.emit() in the catch code block to release the error event manually in order for the listener function to listen.

Run the demo.

$ node demos/18.js

Visit http://127.0.0.1:3000 and you will see logging error in the command line window.

5. The Function Of Web App

5.1 Cookies

ctx.cookies is used to read and write the cookie. Let's take a look at the example below (see the full code here).

// demos/19.js
const main = function(ctx) {
  const n = Number(ctx.cookies.get('view') || 0) + 1;
  ctx.cookies.set('view', n);
  ctx.response.body = n + ' views';
}

Run the demo.

$ node demos/19.js

Visit http://127.0.0.1:3000 and you will see 1 views. It becomes 2 views if you Refresh the page once. And the count will be incremented by 1 each time it is refreshed.

5.2 Forms

Web apps are inseparable from processing forms. Essentially, the form is the key-value pair sent by the POST method to the server. The koa-body module can be used to extract key-value pairs from the data body of the POST request. Let's take a look at the example below (see the full code here).

// demos/20.js
const koaBody = require('koa-body');

const main = async function(ctx) {
  const body = ctx.request.body;
  if (!body.name) ctx.throw(400, '.name required');
  ctx.body = { name: body.name };
};

app.use(koaBody());

Run the demo.

$ node demos/20.js

Open another command line window and run the following command.

$ curl -X POST --data "name=Jack" 127.0.0.1:3000
{"name":"Jack"}

$ curl -X POST --data "name" 127.0.0.1:3000
name required

The above code uses the POST method to send a key-value pair to the server, and the key-value pair is parsed correctly. If the data sent is incorrect, you will receive an error.

5.3 Upload the files

The koa-body module can also be used to upload files. Let's take a look at the example below (see the full code here).

// demos/21.js
const os = require('os');
const path = require('path');
const koaBody = require('koa-body');

const main = async function(ctx) {
  const tmpdir = os.tmpdir();
  const filePaths = [];
  const files = ctx.request.body.files || {};

  for (let key in files) {
    const file = files[key];
    const filePath = path.join(tmpdir, file.name);
    const reader = fs.createReadStream(file.path);
    const writer = fs.createWriteStream(filePath);
    reader.pipe(writer);
    filePaths.push(filePath);
  }

  ctx.body = filePaths;
};

app.use(koaBody({ multipart: true }));

Run the demo.

$ node demos/21.js

Open another command line window and run the following command to upload a file. Note that /path/to/file should be replaced with your real file path.

$ curl --form upload=@/path/to/file http://127.0.0.1:3000
["/tmp/file"]

6. Reference

0 Comment

temp