NodeJs

Revision as of 02:00, 6 November 2018 by Rasimsen (talk | contribs) (How to Dockerize a Node.js application)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)


Install Node.js

If you’ve never worked with Node.js before, kick off with installing the npm manager: nodejs.org/en/download/package-manager

Install NPM and Express Framework

In addition to npm, our application will use the Express Framework, one of the most popular Node.js frameworks. Create a new directory and initialize npm:

$ mkdir helloworld 
$ cd helloworld
$ npm init

When asked for the details of the application (name, version, etc.), just confirm the default values with enter.

Npm will create a package.json that will hold the dependencies of the app. Let’s add the Express Framework as the first dependency:

$ npm install express --save

The file should look like this now:

{
  "name": "helloworld",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.15.2"
  }
}

Details of Hello World

With everything installed, we can create an index.js file with a simple HTTP server that will serve our Hello World website:

//Load express module with `require` directive
var express = require('express')
var app = express()

//Define request response in root URL (/)
app.get('/', function (req, res) {
  res.send('Hello World!')
})

//Launch listening server on port 8081
app.listen(8081, function () {
  console.log('app listening on port 8081!')
})

Run the app

The application is ready to launch:

$ node index.js

Go to http://localhost:8081/ in your browser to view it.


How to Dockerize a Node.js application

You might’ve heard about the whole Docker thing, but that still doesn’t answer the question: “why bother?” Well, that’s why:

  • You can launch a fully capable development environment on any computer supporting Docker; you don’t have to install libraries, dependencies, download packages, mess with config files etc.
  • The working environment of the application remains consistent across the whole workflow. This means the app runs exactly the same for developer, tester, and client, be it on development, staging or production server.


Every application requires a specific working environment: pre-installed applications, dependencies, data bases, everything in specific version. Docker containers allow you to create such environments. Contrary to VM, however, the container doesn’t hold the whole operating system—just applications, dependencies, and configuration. This makes Docker containers much lighter and faster than regular VM’s.

In this part of the guide, we shall launch the previously created app in a Docker container.

Install Docker

Begin with installing Docker for your type of OS:

Write Dockerfile

The Docker container is launched on the basis of a Docker image, a template with the application details. The Docker image is created with instructions written in the Dockerfile. Let’s add Dockerfile to the directory with our application:

Summing up, the whole Dockerfile should look like this:

FROM node:7
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
CMD node index.js
EXPOSE 8081

Line 1: Use another Docker image for the template of my image. We shall use the official Node.js image with Node v7. Line 2: Set working dir in the container to /app. We shall use this directory to store files, run npm, and launch our application. Line 3-5: Copy application to /app directory and install dependencies. If you add the package.json first and run npm install later, Docker won’t have to install the dependencies again if you change the package.json file. This results from the way the Docker image is being built (layers and cache). Line 6: This line describes what should be executed when the Docker image is launching. What we want to do is to run our application Line 7: Expose port 8081 to the outside once the container has launched


Build Docker image

With the instructions ready all that remains is to run the docker build command, set the name of our image with -t parameter, and choose the directory with the Dockerfile:

$ docker build -t hello-world .

Run Docker container

docker run -p 8081:8081 hello-world

Sharing Docker image

Docker images can be hosted and shared in special image registries. The most popular is Docker Hub, a GitHub among Docker registries, in which you can host private and public images. Let’s push an image to Docker Hub now:

  • Sign up at hub.docker.com
  • Build the image again using your Docker Hub credentials:
$ docker build -t [USERNAME]/hello-world .
  • Log in to Docker Hub with your credentials:
$ docker login #username and password will ask!
  • Push the image to Docker Hub:
 $ docker push [USERNAME]/hello-world

You can now use the image on any server or PC with Docker installed:

$ docker run [USERNAME]/hello-world

Deploying dockerized application to server

Running a dockerized app requires a couple of steps:

  • Test if the application is free of errors
  • Build the Docker image
  • Pushing the image to the registry
  • Pull and run the image on the server

Express Microservice Starter

https://www.npmjs.com/package/express-microservice-starter

An express-based bootstrapping module for building microservices with Node.js. The starter utilises sub-app mounting to provide any implementing express application with a variety of functionality.

Key Features

The starter provides the following features out of the box;

  • CORS support
  • Cache-Control header support
  • Body-parsing support
  • Configurable controller/route auto-scanning
  • Configurable health monitors
  • Configurable per-application logging
  • Automatic service registration with ZooKeeper
  • Actuator info and health endpoints (/info and /health)
  • Partial Response Powered by express-partial-response
  • Request correlation id support (res.locals.correlationId and req.log Bunyan child logging also provided)

Basic Usage

The following is the most basic usage of the starter, for a more detailed example please refer to the example directory;


'use strict';
 
var express = require('express');
var micro   = require('express-microservice-starter');
 
var app = express();
 
app.use(micro({ discoverable: false, debug: true }));
 
app.listen(8000, function onListen() {
  log.info('Microservice initialised and accepting requests at the following root: http://localhost:8000/starter/v1');
});
 
app.on('service:registered', function(data) {
  log.info('service registered with zookeeper', data);
  log.info('microservice registration data is also available as an app.locals property', app.locals.microservice);
});

Configuration

By placing an app.yml config file in the /config directory of an implementing app it is possible to override default options.

default:
 
  #
  # Basic
  #
  server:
    port: 8000
 
  #
  # Log
  #
  log:
    path: my-log-file.log
 
  #
  # Microservice
  #
  microservice:
    basePath: services
    server:
      name: starter/v1
      dependencies: my/other/service/to/monitor/v1
      registrationNetworkInterfacePriority: 
        - en0
        - lo0
 
  #
  # Zookeeper
  #
  zookeeper:
    connectionString: localhost:2181
    retry:
      wait: 1000
      count: 5

In the above example the application would be accessible at the following address: http://0.0.0.0:8000/starter/v1, with the /actuator/info and /actuator/health diagnostic endpoints activated.

Note: the registrationNetworkInterfacePriority property allows the selection of the network interface when dynamically registering a service with ZooKeeper.

Note: if registrationNetworkInterfacePriority doesn't work for you, you can set ADVERTISED_HOST and ADVERTISED_PORT environment variable.

If you would like to override the version which is exposed in the /info endpoint, just set the VERSION node env variable.

VERSION=1.0.1 node index.js

API

app.use(micro([options]));


options is an optional argument which can overwrite the defaults. It can take the following properties;

  • debug: boolean Activate finer grained logging.
  • discoverable: boolean Register the service with Zookeeper to allow for discovery by other services connecting to the same instance of Zookeeper.
  • controllersPath: String Path to load controllers. Defaults to controllers.
  • monitorsPath: String Path to load monitors. Defaults to monitors.
  • partialResponseQuery: String The query parameter to use for partial reponse. Defaults to fields.
  • correlationHeaderName: String The name of your correlation header. Defaults to X-CorrelationID.
  • validatorOptions: object Enable express-validator with these options. Defaults to null.
  • enableBodyParsing: boolean Enable or disable body parsing, useful to disable when dealing with content other than JSON. Enables express-validator. Defaults to true.
  • enableEtag: boolean Activate etag. Defaults to false.
  • enableRequestTracing: boolean Enabled request log trace. Defaults to false.

SWAGGER Integration

Supports binding of route handlers using a swagger specification document via the options.swaggerConfig object.

app.js

'use strict';
 
var express = require('express');
var micro   = require('express-microservice-starter');
 
var app = express();
 
var options = {
  swaggerConfig: {
    filePath: `/swagger.yml`, // path to swagger specification file
    controllers: `lib/controllers` // path to controllers directory
  }
};
 
app.use(micro(options));
 
//  to register application specific exception handlers wait until the
//  'swagger:routes:registered' event is emitted.
app.once('swagger:routes:registered', () => {
 
  app.use(function (err, req, res, next) {
  
    if (err) {
      res.status(418).json({
        name: 'Teapot',
        message: 'Always wanted to use this...'
      });
    }
  });
});
 
app.listen(8000, function onListen() {
  log.info('Microservice initialised and accepting requests at the following root: http://localhost:8000/starter/v1');
});

swagger.yml

swagger: "2.0"
info:
  version: 1.0.0
  title: "API specification"
  description: API specification
basePath: /v1
schemes:
  - http
consumes:
  - application/json
produces:
  - application/json
paths:
  /users:
    x-swagger-router-controller: users # lib/controllers/users 
    get:
      description: Return a list of users
      operationId: find
      responses:
        200:
          description: User Response
          schema:
            type: array
            items:
              $ref: '#/definitions/User'
        default:
          description: Standard Error Response
          schema:
            $ref: '#/definitions/Error'
 
  /users/{uuid}:
    x-swagger-router-controller: users # lib/controllers/users 
    get:
      description: Return a user based on the provided uuid
      operationId: get
      parameters:
        - name: uuid
          in: path
          description: UUID of the user to return
          required: true
          type: integer
          format: int64
      responses:
        200:
          description: Item Response
          schema:
            $ref: '#/definitions/User'
        default:
          description: Standard Error Response
          schema:
            $ref: '#/definitions/Error'
 
 
definitions:
  User:
    required:
      - uuid
      - name
      - status
      - created
    properties:
      uuid:
        type: string
      name:
        type: string
      status:
        type: string
        enum:
          - active
          - inactive
      created:
          type: string
          format: date-time

lib/controllers/users.js

'use strict';
 
const user = {
  uuid: '1234567890',
  name: 'User One',
  status: 'active',
  created: new Date('2000-01-01')
};
 
exports.find = function find (req, res, next) {
 
  try {
    res.json([user]);
  }
  catch (e) {
    next(e);
  }
}
 
exports.get = function get (req, res, next) {
 
  try {
    res.json(user);
  }
  catch (e) {
    next(e);
  }
}

Keywords

express, zookeeper, microservicestarter, zoologist