Node.js Koa Container
Tags: node, docker
Published: 2019-10-10 13:33:42

An example of how to create a Docker container application using [Koa.js](http://koajs.com) Next generation web framework for [Node.js](https://nodejs.com/ "Nodejs").

In the project root, initialize using [Yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com/).

    yarn init -y
    

Install dependencies.

    yarn add koa
    yarn add koa-body
    yarn add koa-logger
    yarn add koa-router
    yarn add koa-views
    yarn add swig
    

Create an `app` folder in the project root.

In the app folder, create a folder named `lib`. Then create this `render.js` module in the new lib folder.

##### render.js

```javascript
/**
  * Module dependencies.
  */

const views = require('koa-views');
const path = require('path');

// setup views mapping .html
// to the swig template engine

module.exports = views(path.join(__dirname, '/../views'), {
  map: { html: 'swig' }
});
``` 

In the app folder, create a folder for templates named `views`. Then create this `index.html` template in the new views folder.

##### index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>{{content}}</h1>
</body>
</html>
```

> Using [Emmet](https://emmet.io/), which is [built into VS Code](https://code.visualstudio.com/docs/editor/emmet "Emmet in Visual Studio Code"), you can create the index.html content by entering an exclamation mark on the first line then select the tab key.

In the app folder, create this `app.js` application entrypoint file.

##### app.js

```javascript
const render = require('./lib/render');
const logger = require('koa-logger');
const router = require('koa-router')();
const koaBody = require('koa-body');

const Koa = require('koa');
const app = module.exports = new Koa();

// middleware

app.use(logger());

app.use(render);

app.use(koaBody());

// route definitions

router.get('/', index);

app.use(router.routes());

async function index(ctx) {
  await ctx.render('index', { content: 'Hello World' });
}

// listen

if (!module.parent) app.listen(3000);
``` 

Project structure

project
 ┣ app
 ┃ ┣ lib
 ┃ ┃ ┗ render.js
 ┃ ┣ views
 ┃ ┃ ┗ index.html
 ┃ ┗ app.js
 ┗ package.json


Test the application locally in a browser at [http://localhost:3000](http://localhost:3000). Use <kbd>Ctrl</kbd>+<kbd>C</kbd> to kill the app after verifying it works.

    cd app
    node app.js
    

### Docker

To containerize the application, create a `docker-compose.yml` file in the project root as follows.

##### docker-compose.yml

    version: '3'
    
    services:
      app:
        image: node:alpine
        user: "node"
        working_dir: /home/node/app
        environment:
          - NODE_ENV=production
        ports:
          - "3000:3000"
        volumes:
          - ./app:/home/node/app
          - ./node_modules:/home/node/node_modules
        expose:
          - "3000"
        command: "node app.js"
    

Build, (re)create and start, the container in disconnected mode. The `app` folder is attached as a volume and mapped to the working directory, `/home/node/app` in the container. The `node app.js` command is executed in the containers working directory.

    docker-compose up -d
    

Test the application locally in a browser at [http://localhost:3000](http://localhost:3000). Use <kbd>Ctrl</kbd>+<kbd>C</kbd> to kill the app after verifying it works.

Stop and remove the container and volumes created by `docker-compose up`.

    docker-compose down
    

Build a Docker image for better performance and deployment once initial development is completed. Instead of mapping the local `app` and `node_modules` folders to the container, copy files and folders into the container, set working directories and run commands as needed.

Create this Dockerfile in the project root

##### Dockerfile

    FROM node:alpine
    WORKDIR /home/node
    
    # using wildcard (*) to copy both package.json and package-lock.json
    COPY package*.json /home/node/
    RUN yarn install --production
    
    # create and set app directory as current dir
    WORKDIR /home/node/app
    COPY app/ /home/node/app/
    EXPOSE 3000
    CMD ["node", "app.js"]
    

Build the image and tag it. In the project root run the following command.

    docker build -t myapp-node .
    

Test the new `myapp-node` Docker image using `docker run`. Same URL as before, [http://localhost:3000](http://localhost:3000).

    docker run -u node -w /home/node/app -e NODE_ENV=production -p 3000:3000 --expose 3000 myapp-node node "app.js"
    

Stop the container using `docker stop` followed by the container ID. To get a list of all running containers, use `docker ps --filter status=running`.

That's it!

This post originally published at [jimfrenette.com/docker/node-js-koa-container](https://jimfrenette.com/docker/node-js-koa-container/)