DEV Community

Nick Shulhin
Nick Shulhin

Posted on

Full Web App + CI/CD pipeline in a Hammer Way! ๐Ÿ”จ๐Ÿ”ฅ๐Ÿ› 

VueJS app with NodeJS backend + CI/CD pipeline: Hammer way ๐Ÿ”จ๐Ÿ› 


In this tutorial I will explain how to build a simple, full-scale Web App project with NodeJS backend + complete CI/CD pipeline.

I call it "Hammer way" , because it doesn't use Docker or any architecture... It is not very fancy, however it works ยฏ_(ใƒ„)_/ยฏ

In the end of this tutorial we will be able to have our application running on server, with fully automated pipeline which will build, test and deploy on every push to git repository!

Both projects are already available here:

Backend => Here
Frontend => And here

(Feel free to submit PRs for improvements)

Are you ready?

Letโ€™s go! ๐Ÿ”ฅ


Part One: Our NodeJS backend ๐Ÿ’ป


For the sake of simplicity, we will have a super minimalistic NodeJS backend which will do the only thing: serve a front end.
Project structure will look like this:

./backend
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ circle.yml
โ”œโ”€โ”€ scripts
โ”‚   โ””โ”€โ”€ deploy.sh
โ””โ”€โ”€ server.js

Letโ€™s have a look at server.js:

const express = require('express');

const path = require('path');

const http = require('http');

const app = express();

app.use(express.static('/home/deploy/frontend/'));

app.get('/', (req, res) =>  res.sendFile(path.join('/home/deploy/frontend/index.html')));

http.createServer(app).listen(8080);

Described above code uses express library which loads an index.html file referenced in static folder and serves on port 8080. Not a rocket science, but we need to start somewhere...

What about deploy.sh?

#!/usr/bin/env bash

ssh-keyscan -H "$1" >> ~/.ssh/known_hosts
ssh "deploy@$1" rm -rf ~/backend/*
scp -r ./server "deploy@$1:~/backend/"
scp -r ./package.json "deploy@$1:~/backend/"
scp -r ./package-lock.json "deploy@$1:~/backend/"
ssh $1 "cd /home/deploy/backend
                                   npm i
                                   forever stopall
                                   forever start server.js
                                   โ€œ

This shell script plays a role of automation deployment system.

Before we walk through the shell code, some server setup clarification:

On our server we will create deploy user specifically for this purpose and generate a pair of SSH keys for our CI/CD pipeline (will go through it soon).

We will also install forever js which will run NodeJS process in the background.

There will be two folders in home directory: frontend and backend. These will be locations where CI server will copy files to.

For those who are not familiar with shell variables: that $1 means we are going to pass server host name as a parameter. (It will be executed like: bash deploy.sh my.awesome.website, where my.awesome.website will be placed instead of $1).
In brief, this script removes old version of server files, copies new files from Git repository and restarts background run process of server.js.

But how do we integrate with CircleCI? Here is our magic circle.yml configuration file:

version: 2
jobs:
  build:
    working_directory: ~/backend
    docker:
      - image: circleci/node:4.8.2
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "package.json" }}
      - run:
          name: Install npm dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package.json" }}
          paths:
            - node_modules
  test:
    docker:
      - image: circleci/node:4.8.2
    steps:
      - checkout
      - run:
          name: Test
          command: npm run test

  deploy:
    docker:
      - image: circleci/node:4.8.2
    steps:
      - checkout
      - run:
          name: Deploy
          command: bash ./scripts/deploy.sh my.awesome.website

workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test:
          requires:
            - build
          filters:
            branches:
              only: master
      - deploy:
          requires:
            - build
            - test
          filters:
            branches:
              only: master

Configuration file above declares working directory where our pulled from Git repository will be located, test and deploy steps which will run test and execute shell script we discussed before to copy new files.

And it also contains environment description such as node version installed on docker container.

Awesome! We finished with backend! Yahoo! ๐ŸŽ‰

Now go and push your project to your Git repository!


Part Two: Quick server configuration ๐Ÿ”‘


As I promised, here is more on Linux server configuration:

We need to install NodeJS:

=> Node JS

After that we should install forever.js for background processing (there could be better alternatives, feel free to experiment):

=> Forever JS

And lastly, a pair of SSH keys:

ssh-keygen -t rsa -C โ€œamazing@dev.to"

You will need a private key soon to allow CircleCi perform SSH actions on the instance.


Part Three: Front end! ๐Ÿ˜


It is a time for a VueJS front-end!

The best way to start with VueJS is by using VueCLI. Once installed you can create a new application by running:

vue create frontend

(You can follow official doc: here)

It may take some time to generate a project, and in the end we will have similar structure:

./frontend
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ babel.config.js
โ”œโ”€โ”€ node_modules
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ public
โ””โ”€โ”€ src

Donโ€™t forget to run npm install to install all dependencies.

To test your awesome template website, run: npm run serve.

Can you see it? Amazing, it works! Good job!

Now, letโ€™s add some magicโ€ฆ ๐Ÿ”ฎ

Letโ€™s create a scripts directory in src folder, and put deploy.sh file there which will look like this:

#!/usr/bin/env bash
ls -l
ssh-keyscan -H "$1" >> ~/.ssh/known_hosts
ssh "deploy@$1" "rm -rf ~/frontend/*"
scp -r ./dist/static "deploy@$1:~/frontend/"
scp ./dist/index.html "deploy@$1:~/frontend/"
scp ./dist/service-worker.js "deploy@$1:~/frontend/"

Looks similar, isnโ€™t it?
This script will remove old front-end, and copy new files built by our CircleCi!

And here our circle.yml file we will create in root of the project:

version: 2
jobs:
  deploy:
    docker:
      - image: circleci/node:latest
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: npm i
      - run:
          name: Package
          command: npm run build
      - run:
          name: Deploy
          command: bash ./scripts/deploy.sh my.awesome.website 

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - deploy:
          filters:
            branches:
              only: master

As you can spot, it looks almost the same as a previous one for the server.
However this version builds our frontend project first, and after - performs deployment.

Final project structure will look like this (with our new files):

./frontend
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ babel.config.js
โ”œโ”€โ”€ node_modules
โ”œโ”€โ”€ circle.yml
โ”œโ”€โ”€ scripts
โ”‚   โ””โ”€โ”€ deploy.sh
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ public
โ””โ”€โ”€ src

We did it! Yahoo!

Now the last piece: CI configuration (CircleCi in this case)


Part Four: CircleCi ๐Ÿ”„


Once you login with BitBucket to CircleCi, add your projects by following them:

Follow project

Do you still remember SSH key we generated on our server? Now we need to use it!

Go to settings of each of two projects, navigate to SSH Permissions tab and copy/paste generated private key there:

SSH keys

And now try to push any change to either frontend or backend project => it will trigger a build and your Web Application will be automatically updated:

First build

We made it! ๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰

Victory


Conclusion ๐Ÿ•


This kind of pipeline would be suitable for a small personal project, or just to get familiar with deployment idea. Sure thing: almost every part of this tutorial can be upgraded and enhanced :) If you have any ideas or spotted a bug (I'm sure there are few of them - don't hesitate to rase PRs!)

Top comments (0)