Welcome to the guide on automating a blog post app deployment using GitHub Actions, Node.js,

CouchDB, and Aptible.

This comprehensive tutorial will guide you in building, deploying, and managing a blog post application using the tools and technologies above.

But first, let me give you a brief overview of the Blogpost app, what it does, and its main components. The Blogpost app is a web application that allows users to create and share blog posts.

Users can write, edit, delete, and view posts by other users. The app uses Node.js as the backend, CouchDB as the database, and GitHub Actions as the continuous integration and deployment tool.

Why did I choose these? Well, there are many reasons, but here are some of the main ones:

Prerequisites

Before starting on the development journey, setting up the necessary tools and technologies is essential.

Developing the Blogpost App

Step 1: Set Up Your Node.js Project

npm init -y

npm install express nano

Step 2: Set Up CouchDB

Step 3: Create Your Node.js Application

const express = require("express");
const nano = require("nano")("http://admin:[email protected]:5984");
const app = express();
const port = 3000;

// Middleware to parse JSON data
app.use(express.json());

// async function asyncCall() {
//   // await nano.db.destroy("alice");
//   await nano.db.create("blogposts");
//   const db = nano.use("blogposts");
//   return response;
// }
// asyncCall();

const db = nano.use("blogposts");

// Create a new blog post
app.post("/posts", async (req, res) => {
  const { title, description, author } = req.body;

  try {
    const doc = await db.insert({
      title,
      description,
      author,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    res.json({ id: doc.id, rev: doc.rev });
  } catch (err) {
    console.error(err);
    res.status(500).send("Error creating post");
  }
});

// Get all blog posts
app.get("/posts", async (req, res) => {
  try {
    const docs = await db.list({ include_docs: true });
    res.json(docs.rows);
  } catch (err) {
    console.error(err);
    res.status(500).send("Error retrieving posts");
  }
});

// Get a specific blog post
app.get("/posts/:id", async (req, res) => {
  const { id } = req.params;

  try {
    const doc = await db.get(id);
    res.json(doc);
  } catch (err) {
    console.error(err);
    res.status(404).send("Post not found");
  }
});

// Update a blog post
app.put("/posts/:id", async (req, res) => {
  const { id } = req.params;
  const { title, description } = req.body;

  try {
    await db.insert({
      _id: id,
      title,
      description,
      updatedAt: new Date(),
    });

    res.json({ message: "Post updated successfully" });
  } catch (err) {
    console.error(err);
    res.status(500).send("Error updating post");
  }
});

// Delete a blog post
app.delete("/posts/:id", async (req, res) => {
  const { id } = req.params;

  try {
    await db.destroy(id);
    res.json({ message: "Post deleted successfully" });
  } catch (err) {
    console.error(err);
    res.status(500).send("Error deleting post");
  }
});

app.listen(port, () => {
  console.log(`Blogpost app listening on port ${port}`);
});

Testing it Locally:

Thorough testing is crucial to ensure the functionality and robustness of your project. Here's a sample API documentation to guide your testing process:

API Documentation

Base URL:

Assuming your server is running locally at port 3000, the base URL for your API would be:

http://localhost:3000

API Endpoints:

a. Create a New Blog Post

b. Get All Blog Posts

c. Get a Specific Blog Post

d. Update a Blog Post

e. Delete a Blog Post

Please replace your-post-id with an actual ID of the blog post when testing the GET, PUT, and DELETE requests.

Step 4: Dockerize Your Node.js Application

You need to have a Docker Hub account. If you haven't created one yet, sign up at Docker Hub.

Ensure you have Docker installed and running on your local machine.

Steps to Push Dockerized App to Docker Hub:

# Use an official Node.js runtime as the base image
FROM node:16

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./

# Install app dependencies
RUN npm install

# Copy the rest of the application files to the working directory
COPY . .

# Expose the port the app runs on
EXPOSE 3000

# Define the command to run the app
CMD ["node", "blog.js"]

Run the following command to build your Docker image and tag it with your Docker Hub username and desired repository name:

 docker build -t your-docker-username/blogpost-app:latest .

Replace your-docker-username with your Docker Hub username and blogpost-app with your desired repository name.

You will get a similar response like this:

[+] Building 1.1s (10/10) FINISHED                                                                                              docker:desktop-linux
 => [internal] load .dockerignore                                                                                                               0.0s
 => => transferring context: 2B                                                                                                                 0.0s
 => [internal] load build definition from Dockerfile                                                                                            0.0s
 => => transferring dockerfile: 489B                                                                                                            0.0s
 => [internal] load metadata for docker.io/library/node:16                                                                                      1.0s
 => [1/5] FROM docker.io/library/node:16@sha256:f77a1aef2da8d83e45ec990f45df50f1a286c5fe8bbfb8c6e4246c6389705c0b                                0.0s
 => [internal] load build context                                                                                                               0.0s
 => => transferring context: 45.31kB                                                                                                            0.0s
 => CACHED [2/5] WORKDIR /usr/src/app                                                                                                           0.0s
 => CACHED [3/5] COPY package*.json ./                                                                                                          0.0s
 => CACHED [4/5] RUN npm install                                                                                                                0.0s
 => CACHED [5/5] COPY . .                                                                                                                       0.0s
 => exporting to image                                                                                                                          0.0s
 => => exporting layers                                                                                                                         0.0s
 => => writing image sha256:c5f046a9b99389aea6bf3f503e9b05cce953daf1b3f77ee5fb3f7469dc36c709                                                    0.0s
 => => naming to docker.io/wise4rmgod/blogpost-app:latest    

 docker login

Enter your Docker Hub username and password when prompted.

Authenticating with existing credentials...
Login Succeeded

 docker push your-docker-username/blogpost-app:latest

This command uploads your local image to Docker Hub under your specified repository.

Step 5: Deploy your project to Aptible

This tutorial assumes you have a basic understanding of setting up an environment, application, endpoint, and database on the Aptible platform. The tutorial utilizes CouchDB as the database and employs Direct Docker for deployment.

aptible login

You will be prompted to enter your email and password. If successful, you will receive a response similar to this:

Token written to /Users/wisdomnwokocha/.aptible/tokens.json
This token will expire after 6 days, 23 hrs (use --lifetime to customize)

Syntax:

aptible deploy --app <app name> --docker-image <docker image in cloud>

Here is an example command:

aptible deploy --app reactgame --docker-image wise4rmgod/blogpost-app

You will receive a response similar to the following:

INFO -- : Starting App deploy operation with ID: 61135861
INFO -- : Deploying without git repository
INFO -- : Writing .aptible.env file...
INFO -- : Fetching app image: wise4rmgod/blogpost-app...
INFO -- : Pulling from wise4rmgod/blogpost-app
INFO -- : 26ee4ff96582: Pulling fs layer
INFO -- : 446eab4103f4: Pulling fs layer
INFO -- : 2e3c22a0f840: Pulling fs layer
INFO -- : a7ab8ad9b408: Pulling fs layer
INFO -- : 3808fdf0c601: Pulling fs layer
INFO -- : ab9e4075c671: Pulling fs layer
INFO -- : 362360c8cef6: Pulling fs layer
INFO -- : 928b5d11ac66: Pulling fs layer
INFO -- : dc87e077ac61: Pulling fs layer
INFO -- : f108e80f4efc: Pulling fs layer
INFO -- : 84ac53840ac8: Pulling fs layer
INFO -- : e81f21b79a1f: Pulling fs layer
INFO -- : 2e3c22a0f840: Downloading: 523 KB / 49.8 MB
INFO -- : 446eab4103f4: Downloading: 173 KB / 16.6 MB
INFO -- : 26ee4ff96582: Downloading: 483 KB / 47 MB
INFO -- : 2e3c22a0f840: Downloading: 25.7 MB / 49.8 MB
INFO -- : a7ab8ad9b408: Downloading: 528 KB / 175 MB
INFO -- : ab9e4075c671: Downloading: 355 KB / 33.4 MB
INFO -- : a7ab8ad9b408: Downloading: 35.3 MB / 175 MB
INFO -- : 26ee4ff96582: Pull complete
INFO -- : 446eab4103f4: Pull complete
INFO -- : 2e3c22a0f840: Pull complete
INFO -- : a7ab8ad9b408: Downloading: 71.2 MB / 175 MB
INFO -- : a7ab8ad9b408: Downloading: 106 MB / 175 MB
INFO -- : a7ab8ad9b408: Downloading: 142 MB / 175 MB

INFO -- : a7ab8ad9b408: Pull complete
INFO -- : 3808fdf0c601: Pull complete
INFO -- : ab9e4075c671: Pull complete
INFO -- : 362360c8cef6: Pull complete
INFO -- : 928b5d11ac66: Pull complete
INFO -- : dc87e077ac61: Pull complete
INFO -- : f108e80f4efc: Pull complete
INFO -- : 84ac53840ac8: Pull complete
INFO -- : e81f21b79a1f: Pull complete
INFO -- : Digest: sha256:de9d04d069ca89ebdb37327365a815c88cd40d90cbc5395cc31c351fff1206dd
INFO -- : Status: Downloaded newer image for wise4rmgod/blogpost-app:latest
INFO -- : No Procfile found in git directory or /.aptible/Procfile found in Docker image: using Docker image CMD
INFO -- : No .aptible.yml found in git directory or /.aptible/.aptible.yml found in Docker image: no before_release commands will run
INFO -- : Pushing image dualstack-v2-registry-i-0a5ec8cff8e775b34.aptible.in:46022/app-63213/72184c41-7dc6-4313-b10e-749125f72577:latest to private Docker registry...
INFO -- : The push refers to repository [dualstack-v2-registry-i-0a5ec8cff8e775b34.aptible.in:46022/app-63213/72184c41-7dc6-4313-b10e-749125f72577]
INFO -- : dd387bc6b362: Pushed
INFO -- : 586bd9d5efcf: Pushed
INFO -- : 8ae0c889ca84: Pushed
INFO -- : c91ec53bcc27: Pushing: 522 KB / 93.6 MB
INFO -- : aec897bac4f0: Pushed
INFO -- : 0ead224631d3: Pushed
INFO -- : ad3b30eb29d3: Pushing: 542 KB / 444 MB
INFO -- : 2a7587eb01b6: Pushing: 544 KB / 137 MB
INFO -- : be36d2a441aa: Pushed
INFO -- : 03f6e3800bbe: Pushed
INFO -- : a10e482288d1: Pushing: 338 KB / 30.7 MB
INFO -- : f9cfc9f6b603: Pushing: 513 KB / 103 MB
INFO -- : c91ec53bcc27: Pushing: 31.3 MB / 93.6 MB
INFO -- : c91ec53bcc27: Pushing: 62.7 MB / 93.6 MB
INFO -- : ad3b30eb29d3: Pushing: 44.5 MB / 444 MB
INFO -- : 2a7587eb01b6: Pushing: 34.4 MB / 137 MB
INFO -- : a10e482288d1: Pushed
INFO -- : ad3b30eb29d3: Pushing: 88.9 MB / 444 MB
INFO -- : f9cfc9f6b603: Pushing: 34.6 MB / 103 MB
INFO -- : 2a7587eb01b6: Pushing: 68.9 MB / 137 MB
INFO -- : ad3b30eb29d3: Pushing: 133 MB / 444 MB
INFO -- : f9cfc9f6b603: Pushing: 70.2 MB / 103 MB
INFO -- : c91ec53bcc27: Pushed
INFO -- : 2a7587eb01b6: Pushing: 103 MB / 137 MB
INFO -- : ad3b30eb29d3: Pushing: 178 MB / 444 MB
INFO -- : ad3b30eb29d3: Pushing: 224 MB / 444 MB
INFO -- : 2a7587eb01b6: Pushed
INFO -- : f9cfc9f6b603: Pushed
INFO -- : ad3b30eb29d3: Pushing: 270 MB / 444 MB
INFO -- : ad3b30eb29d3: Pushing: 312 MB / 444 MB
INFO -- : ad3b30eb29d3: Pushing: 355 MB / 444 MB
INFO -- : ad3b30eb29d3: Pushing: 401 MB / 444 MB
INFO -- : ad3b30eb29d3: Pushed
INFO -- : latest: digest: sha256:de9d04d069ca89ebdb37327365a815c88cd40d90cbc5395cc31c351fff1206dd size: 2841
INFO -- : Pulling from app-63213/72184c41-7dc6-4313-b10e-749125f72577
INFO -- : Digest: sha256:de9d04d069ca89ebdb37327365a815c88cd40d90cbc5395cc31c351fff1206dd
INFO -- : Status: Image is up to date for dualstack-v2-registry-i-0a5ec8cff8e775b34.aptible.in:46022/app-63213/72184c41-7dc6-4313-b10e-749125f72577:latest
INFO -- : Image app-63213/72184c41-7dc6-4313-b10e-749125f72577 successfully pushed to registry.
INFO -- : STARTING: Register service cmd in API
INFO -- : COMPLETED (after 0.28s): Register service cmd in API
INFO -- : STARTING: Derive placement policy for service cmd
INFO -- : COMPLETED (after 0.15s): Derive placement policy for service cmd
INFO -- : STARTING: Create new release for service cmd
INFO -- : COMPLETED (after 0.24s): Create new release for service cmd
INFO -- : STARTING: Schedule service cmd
..
INFO -- : COMPLETED (after 13.49s): Schedule service cmd
INFO -- : STARTING: Stop old app containers for service cmd
INFO -- : COMPLETED (after 0.0s): Stop old app containers for service cmd
INFO -- : STARTING: Start app containers for service cmd
INFO -- : WAITING FOR: Start app containers for service cmd

INFO -- : WAITING FOR: Start app containers for service cmd
INFO -- : COMPLETED (after 18.4s): Start app containers for service cmd
INFO -- : STARTING: Delete old containers for service cmd in API
INFO -- : COMPLETED (after 0.0s): Delete old containers for service cmd in API
INFO -- : STARTING: Commit app containers in API for service cmd
INFO -- : COMPLETED (after 0.26s): Commit app containers in API for service cmd
INFO -- : STARTING: Commit service cmd in API
INFO -- : COMPLETED (after 0.13s): Commit service cmd in API
INFO -- : STARTING: Cache maintenance page
INFO -- : COMPLETED (after 0.28s): Cache maintenance page
INFO -- : STARTING: Commit app in API
INFO -- : COMPLETED (after 0.19s): Commit app in API
INFO -- : App deploy successful.

Step 6: Create GitHub Actions Workflow:

on:
  push:
      branches: [ main ]

env:
  IMAGE_NAME: user/app:latest
  APTIBLE_ENVIRONMENT: "my_environment"
  APTIBLE_APP: "my_app"


jobs:
  deploy:
    runs-on: ubuntu-latest

      # Allow multi platform builds.
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2

      # Allow use of secrets and other advanced docker features.
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      # Log into Docker Hub
      - name: Login to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # Build image using default dockerfile.
      - name: Build and push
        uses: docker/build-push-action@v3
        with:
          push: true
          tags: ${{ env.IMAGE_NAME }}

      - name: Deploy to Aptible
        uses: aptible/aptible-deploy-action@v1
        with:
          username: ${{ secrets.APTIBLE_USERNAME }}
          password: ${{ secrets.APTIBLE_PASSWORD }}
          environment: ${{ env.APTIBLE_ENVIRONMENT }}
          app: ${{ env.APTIBLE_APP }}
          docker_img: ${{ env.IMAGE_NAME }}
          private_registry_username: ${{ secrets.DOCKERHUB_USERNAME }}
          private_registry_password: ${{ secrets.DOCKERHUB_TOKEN }}

Adjust the branch name main, Docker Hub username yourusername, and repository name yourrepository , aptible_username, aptible_password, and environment to match your configurations.

Set Up Secrets:

Conclusion

This comprehensive tutorial will help you build, deploy, and manage a blog post application with Node.js, CouchDB, and Aptible.

You've grasped the fundamentals of setting up the essential tools and technologies, crafting the backend of the blog post application, dockerizing the application, pushing the Docker image to Docker Hub, and deploying the application to Aptible.

Congratulations on completing this comprehensive tutorial and your journey into cloud-native application development using Aptible!