What is a Koa, what is the difference between this framework and Express.js? Andrew Yasynyshyn, CEO of Ralabs, shares tutorial for your first steps in Koa.

I’m going to show you the basics of Koa in this short 7-step tutorial. Koa is a new framework for web applications and APIs that aims to be potent and expressive, but at the same time small and simple. Created by a part of the Express.js team, Koa with its elegant suite of methods makes writing servers convenient and enjoyable. In this framework, we are able to ditch callbacks and expand error-handling due to leveraging generators. More information about the framework you can find on this website. Requirements of using async functions in Koa in versions of node < 7.6: if you don’t have installed npm, you can use nvm. In this short tutorial, I will use github styled change markers + or -. If there is +, that means something you need to add; if there is - before the line, that means something you need to remove.

1. Setup

$ mkdir sample_api
$ cd sample_api
Then you need to initialize your project using npm - $ npm init -y. When the above is over, you must add the first Koa dependency: $ npm i koa. Than create a new server.js file in your app’s root and add the following lines:
// ./server.js

const Koa = require('koa');

const server = new Koa();
server.listen(3001);
After that, you can run your server with $ node server.js. If you open http://localhost:3001 in your browser, you will see ‘Not found’ message because there are no middlewares to handle requests.

2. First KOA middleware

Koa is a middleware framework that can use two different kinds of functions as middleware:
  • async function
  • common function
Want to know more? Read these KOA middleware docs.Let’s go back to codding. Open server.js in your editor again and add:
// ./server.js

– server.listen(3001)

+ server.use(ctx => {
+   ctx.body = ‘I am your first KOA API!’
+ })
+ .listen(3001)
After these changes, the content of /server must look like:
// ./server.js

const Koa = require('koa')
const server = new Koa()

server
 .use(ctx => {
  ctx.body = 'I am your first KOA API!'
 })
 .listen(3001)
You’ll need to stop the previous process by pressing on (control) + C and start it again using the command node server.js. You can avoid repeating this operation every time by setting up nodedemon. Run $ npm i –save-dev nodemon. Then go to package.json and add:
"scripts": {
 "start": "nodemon server.js"
},
Run the app using $ npm start. Run app, go to http://localhost:3001, and you’ll see I am your first KOA API!.

3. Logger

It’s always a good idea to add some kind of logging. We are going to use an Express port of morgan package.Run command $ npm i koa-morgan. Then open ./server.js again and add these lines:
// ./server.js

const Koa = require('koa')

+ const logger = require('koa-morgan')

// …

server
+ .use(logger('tiny'))

// …
Now ./server.js content should look like:
// ./server.js

const Koa = require('koa')
const logger = require('koa-morgan')

const server = new Koa()

server
 .use(logger('tiny'))
 .use(ctx => {
  ctx.body = 'I am your first KOA API!'
 })
 .listen(3001)
Notice, each received request will log into the console after that step. It should look similar to:
GET / 200 24 – 2.392 ms

4. Router

The next step is to add a router so that our server can behave differently depending on the requests. Run $ npm i koa-router. Open ./server.js and add the lines:
// ./server.js
// ...
+ const Router = require('koa-router')
// ...

+ const router = new Router()

+ router.get('/', ctx => {
+   ctx.body = 'I am root!'
+ });

+ router.get('/second_route', ctx => {
+   ctx.body = 'I am second_route'
+ })

+ router.post('/something', ctx => {
+   ctx.body = {
+     something: 'something here'
+   }
+ })

// ...

server
.use(logger('tiny'))
+ .use(router.routes())
- .use(ctx => {
-   ctx.body = 'I am your first KOA API!'
- })
// ...
After that ./server.js content must look like:
// ./server.js
const Koa = require('koa')
const logger = require('koa-morgan')
const Router = require('koa-router')

const server = new Koa()
const router = new Router()

router.get('/', ctx => {
  ctx.body = 'I am root!'
});

router.get('/second_route', ctx => {
  ctx.body = 'I am second_route'
})

router.post('/something', ctx => {
  ctx.body = {
    something: 'something here'
  }
})

server
  .use(logger('tiny'))
  .use(router.routes())
  .listen(3001)
In each route, you will see a corresponding response declared on the router request body. I recommend using curl or postman for API routes testing.

5. Parsing requests

Currently, our API app doesn't know how to handle request body, and it can't parse JSON or form-data, for example. To fix that you need to add another middleware koa-body: Run $ npm i koa-body Open ./server.js and add the lines:
// ./server.js
// ...
+ const bodyParser = require('koa-body')()
You should use this middleware only if you need to parse incoming request from JSON, form-data, etc.. It is necessary to use it for POST, PUT, etc. Example:
router.post('/comments', bodyParser, async ctx => {
  const comment = await Comment.create(ctx.request.body.comment)
  ctx.body = {
    comment: comment
  }
})
The complete file must look like:
// ./server.js
const Koa = require('koa')
const logger = require('koa-morgan')
const Router = require('koa-router')
const bodyParser = require('koa-body')()

const server = new Koa()
const router = new Router()

router.get('/', ctx => {
  ctx.body = 'I am root!'
})

router.get('/second_route', ctx => {
  ctx.body = 'I am second_route'
})

router.post('/something', ctx => {
  ctx.body = {
    something: 'something here'
  }
})

server
  .use(logger('tiny'))
  .use(router.routes())
  .listen(3001)

6. DB setup and first Model

In this tutorial, we are going to use Sequalize as our ORM and SQLite3 as our database. First, you need to install few dependencies: $ npm i sequelize sqlite3 sequelize-cli. Now you can initialize sequelize cli with a command: $ node_modules/.bin/sequelize init. After this command, few files and folders will be created: 
  • ./config/config.json - this file contains sequelize database connection configs;
  • ./models - contains all our models;
  • ./models/index.js - responsible for auto-loading models;
  • ./migrations - here all our migrations will live;
  • ./seeders - here our data-migrations/seeds will be defined.
 The next step is to update configs in ./config/config.json. You should replace its content with:
{
  "development": {
    "username": "usr",
    "password": "psw",
    "database": "sample_api",
    "host": "127.0.0.1",
    "dialect": "sqlite"
  }
}
Then you can generate a new model using the command: $ node_modules/.bin/sequelize model:create --name Comment --attributes text:string The last step is to migrate our db. You can do that by running: $ node_modules/.bin/sequelize db:migrate If you want to know more about Sequelize, please read its official documentation. We are going to use SQLite3 for koa into. Considering SQLite3 limitations, please, change it to something more ready for production.

7. Comments CRUD

Now you need to create routes for all REST CRUD actions for our comments resource. Open again our ./server.js file and add these lines:
// ./server.js

// ...

+ const models = require('./models')

// ...
+ router.get('/comments', async ctx => {
+   const comments = await models.Comment.findAll()
+   ctx.body = {
+     comments
+   }
+ });
+
+ router.get('/comments/:id', async ctx => {
+   const comment = await models.Comment.findOne({where: { id: ctx.params.id }})
+   ctx.body = {
+     comment
+   }
+ })
+
+ router.put('/comments/:id', bodyParser, async ctx => {
+   let comment = await models.Comment.findOne({where: { id: ctx.params.id }})
+   comment = await comment.update(ctx.request.body.comment)
+   ctx.body = {
+     comment
+   }
+ })
+
+ router.post('/comments', bodyParser, async ctx => {
+   const comment = await models.Comment.create(ctx.request.body.comment)
+   ctx.body = {
+     comment
+   }
+ })

+ router.del('/comments/:id', bodyParser, async ctx => {
+   let comment = await models.Comment.findOne({where: { id: ctx.params.id }})
+   comment = await comment.destroy()
+   ctx.body = {
+     comment
+   }
+ })
The complete file must look like:
// ./server.js
const Koa = require('koa')
const logger = require('koa-morgan')
const Router = require('koa-router')
const bodyParser = require('koa-body')()

const models = require('./models')

const server = new Koa()
const router = new Router()

router.get('/comments', async ctx => {
  const comments = await models.Comment.findAll()
  ctx.body = {
    comments
  }
});

router.get('/comments/:id', async ctx => {
  const comment = await models.Comment.findOne({where: { id: ctx.params.id }})
  ctx.body = {
    comment
  }
})

router.put('/comments/:id', bodyParser, async ctx => {
  let comment = await models.Comment.findOne({where: { id: ctx.params.id }})
  comment = await comment.update(ctx.request.body.comment)
  ctx.body = {
    comment
  }
})

router.post('/comments', bodyParser, async ctx => {
  const comment = await models.Comment.create(ctx.request.body.comment)
  ctx.body = {
    comment
  }
})

router.del('/comments/:id', bodyParser, async ctx => {
  let comment = await models.Comment.findOne({where: { id: ctx.params.id }})
  comment = await comment.destroy()
  ctx.body = {
    comment
  }
})

server
.use(logger('tiny'))
.use(router.routes())
.listen(3001)
Notice, we updated controller middlewares for using async functions. It's basically the most valuable feature of KOA. With async functions, KOA allows to write asynchronous code in a synchronous manner and avoid callback-hell. Finally, you can open Postman and test all endpoints. That's all! Tnx!