This tutorial covers:

  1. Creating a FeathersJS API
  2. Creating and setting up a project on Heroku
  3. Automating deployment to Heroku

Automation goes beyond just building solutions to replace complex or time-consuming manual processes. As the popular saying goes, “anything that can be automated should be automated.” For example, deploying updates to applications can and should be automated. In this tutorial, I will show you how to set up hands-free deployment of a FeathersJS app to Heroku. I will guide you through implementing a continuous deployment (CD) pipeline to release updates to an application as soon as changes are pushed. The sample application we will be working with is an API to manage quiz questions. It provides endpoints to create, read, update and delete quiz questions.

Prerequisites

Before you start, make sure these items are installed on your system:

  • A minimum NodeJS version of 10.0.0
  • An up to date JavaScript packages manager such as NPM or Yarn
  • The Feathers CLI. You can install it by running this command:
npm install -g @feathersjs/cli

For repository management and continuous integration/continuous deployment, you need:

Our tutorials are platform-agnostic, but use CircleCI as an example. If you don’t have a CircleCI account, sign up for a free one here.

Why FeathersJS and Heroku?

FeathersJS is a lightweight web framework for creating real-time applications and REST APIs using JavaScript or TypeScript. The FeathersJS toolset includes an architecture pattern that makes it easy to create scalable REST APIs and real-time applications. You can build prototypes in minutes and production-ready apps in days.

Heroku is a platform as a service (PaaS) that enables developers to build, run, and operate applications entirely in the cloud. By taking care of the more demanding aspects of maintaining a cloud-based application, Heroku allows developers to focus on building apps instead of maintaining servers.

Getting started

Create a new folder for the project:

mkdir feathers_heroku

cd feathers_heroku

Next, generate a new app using the Feathers CLI generate command.

feathers generate app

For this project, we will be using JavaScript. Also, we only want to make a REST API. Respond to the questions from the CLI as listed here:

? Do you want to use JavaScript or TypeScript? JavaScript
? Project name feathers-heroku
? Description
? What folder should the source files live in? src
? Which package manager are you using (has to be installed globally)? npm
? What type of API are you making? REST
? Which testing framework do you prefer? Jest
? This app uses authentication No
? Which coding style do you want to use? ESLint

Before you do anything else, update the index page to let visitors know that the API is under construction. In public/index.html, add an ‘under construction’ message to the main element:

<main class="container">
  <!--  Image declaration-->
  <h1 class="center-text">This API is under construction</h1>
  <!--  Footer declaration-->
</main>

Next, run your app using this following command:

npm run dev

By default, the application runs at port 3030. Enter http://localhost:3030/ in your browser to go there.

FeathersJS app homepage

Configuring Heroku

Your next step is creating a new application on Heroku. You can do this from the Heroku dashboard. Click New, and then click New App. Fill in the form as shown. You can use a different name and region for the app if you want.

Create a Heroku app

Click the Create app button. You will then be redirected to the Deploy view for your newly created application.

Next, you need to add a buildpack. Click the Settings tab. In the buildpacks section, click Add buildpack.

Add a buildpack

On the form that opens, you can either select an officially supported buildpack or provide a URL for your buildpack. Select nodejs to use the officially supported Heroku nodejs buildpack. Click Save changes. Node.js will be used to build your next deployment.

The last thing you need is an API key. You will use this to connect your CircleCI pipeline to Heroku. To get your API key, open the account settings page and scroll down to the API key section.

Heroku API key

Click the Reveal button and copy the API key that is displayed.

Configuring CircleCI

Next, we need to add the pipeline configuration for CircleCI. For this project, the pipeline will consist of two steps:

  1. Build. Here we build the project and install the project dependencies. Ideally, we should run project tests at this stage. However, to keep this tutorial a reasonable length, we will skip testing.
  2. Deploy to Heroku. If the build stage is completed successfully, you can deploy the latest changes to Heroku.

At the root of your project, create a folder named .circleci. Within it, create a file named config.yml. In the newly created file, add this configuration:

# Use the latest 2.1 version of CircleCI pipeline process engine.
version: 2.1

orbs:
  heroku: circleci/heroku@1.2.6
  node: circleci/node@4.7.0

jobs:
  build:
    executor: node/default
    steps:
      - checkout
      - node/install-packages:
          cache-path: ~/project/node_modules
          override-ci-command: npm install

workflows:
  sample:
    jobs:
      - build
      - heroku/deploy-via-git:
          force: true # this parameter instructs the push to use a force flag when pushing to the heroku remote, see: https://devcenter.heroku.com/articles/git
          requires:
            - build

This configuration pulls in the Heroku orb circleci/heroku. This orb gives you access to a powerful set of Heroku jobs and commands. One of those jobs, heroku/deploy-via-git, deploys your application straight from your GitHub repo to your Heroku account. The config uses the Node.js orb circleci/node, which allows you to install packages with caching enabled by default.

Also specified in the config, a job named build checks out the latest code and installs the packages specified in the package.json file.

Finally, there is a workflow that runs the build job followed by the heroku/deploy-via-git job. Note that there is a requires option that tells CircleCI to run deploy-via-git only if the build job has been completed.

Next, set up a repository on GitHub and link the project to CircleCI. See this post for help: Pushing your project to GitHub.

Log into your CircleCI account. If you signed up with your GitHub account, all your repositories will be displayed on your project’s dashboard.

Next to your feathers_heroku project, click Set Up Project.

CircleCI detects the config.yml file within the project. Click Use Existing Config and then Start Building. Your first workflow will start running, however, it will fail! That is to be expected.

Pipeline build fails

The deployment process fails because you have not provided your Heroku API key. Go ahead and fix that now. Click the Project Settings button, then click Environment Variables. Add two new variables as follows:

  • The HEROKU_APP_NAME variable is the app name in Heroku (deno-heroku-circleci).
  • The HEROKU_API_KEY variable is for the Heroku API key you retrieved from the account settings page.

Select the Rerun Workflow from Failed option to rerun the Heroku deployment. To confirm that your workflow was successful, you can open your newly deployed app in your browser. The URL for your application should be in this format: https://<HEROKU_APP_NAME>.herokuapp.com/.

You will be greeted by your under-construction index page.

Implementing the questions service

It is time to add the functionality to handle questions on our API. For this tutorial, a question will have fields for these attributes:

  • Difficulty
  • Question
  • Correct answer

A unique primary key is assigned by default when the question is created.

Create a database-backed service using this command:

feathers generate service

For this service, use NeDB, which you can confirm by just pressing Enter. Use Questions as the service name and confirm all other prompts with the defaults. Just press Enter:

  ? What kind of service is it? NeDB
  ? What is the name of the service? Questions
  ? Which path should the service be registered on? /questions
  ? What is the database connection string? nedb://../data

With this command, FeathersJS has provided everything you need for CRUD operations on your questions. At this point, you have an API with these endpoints:

  • GET /questions lists all questions page by page.
  • POST /questions creates a new question.
  • GET questions/123 returns the details of the question with id 123. You can also include queries in this request: questions/123?difficulty=medium.
  • PATCH /questions/123 and PUT /questions/123 update the details of the question with id 123.
  • DELETE /questions/123 deletes the question with id 123.

You can serve your application and make requests to any of the endpoints. For this tutorial, seed the database and override some default functionality before taking your shiny new API for a spin.

When you receive a request to add a question, you want to retrieve only the three earlier specified values from the request before saving them to the database. Do that by overriding the create service method. Open src/services/questions/questions.class.js and edit it to match this code:

// src/services/questions/questions.class.js
const { Service } = require("feathers-nedb");

exports.Questions = class Questions extends Service {
  create(data, params) {
    const { question, correctAnswer, difficulty } = data;
    const quizData = { question, correctAnswer, difficulty };
    return super.create(quizData, params);
  }
};

Next, create a seeder that will give you some sample questions. In the src/services/questions directory, create a new file called questions.seed.js. Add this code to the newly created file:

// src/services/questions/questions.seed.js
exports.seedQuestions = [
  {
    difficulty: "medium",
    question: "The HTML5 standard was published in 2014.",
    correctAnswer: "True",
  },
  {
    difficulty: "medium",
    question:
      "Which computer hardware device provides an interface for all other connected devices to communicate?",
    correctAnswer: "Motherboard",
  },
  {
    difficulty: "medium",
    question: "On which day did the World Wide Web go online?",
    correctAnswer: "December 20, 1990",
  },
  {
    difficulty: "medium",
    question: "What is the main CPU is the Sega Mega Drive / Sega Genesis?",
    correctAnswer: "Motorola 68000",
  },
  {
    difficulty: "medium",
    question: "Android versions are named in alphabetical order.",
    correctAnswer: "True",
  },
  {
    difficulty: "medium",
    question:
      "What was the first Android version specifically optimized for tablets?",
    correctAnswer: "Honeycomb",
  },
  {
    difficulty: "medium",
    question:
      "Which programming language shares its name with an island in Indonesia?",
    correctAnswer: "Java",
  },
  {
    difficulty: "medium",
    question: "What does RAID stand for?",
    correctAnswer: "Redundant Array of Independent Disks",
  },
  {
    difficulty: "medium",
    question:
      "Which of the following computer components can be built using only NAND gates?",
    correctAnswer: "ALU",
  },
  {
    difficulty: "medium",
    question:
      "What was the name of the security vulnerability found in Bash in 2014?",
    correctAnswer: "Shellshock",
  },
];

Next, open src/services/questions/questions.service.js and edit it to match this code:

// src/services/questions/questions.service.js
// Initializes the `Questions` service on path `/questions`
const { Questions } = require("./questions.class");
const createModel = require("../../models/questions.model");
const hooks = require("./questions.hooks");
const { seedQuestions } = require("./questions.seed");

module.exports = async function (app) {
  const options = {
    Model: createModel(app),
    paginate: app.get("paginate"),
  };

  // Initialize our service with any options it requires
  app.use("/questions", new Questions(options, app));

  // Get our initialized service so that we can register hooks
  const service = app.service("questions");

  //get the total number of questions in the database
  const { total: totalQuestions } = await service.find({
    query: {
      $limit: 0,
    },
  });

  //seed the database if there are no questions saved
  if (totalQuestions === 0) {
    await seedQuestions.forEach((question) => {
      service.create(question);
    });
  }

  service.hooks(hooks);
};

Observe that the code in this snippet first checks the number of questions in the database, and seeds it only if there are none. This prevents the seeder from running every time you start up the local server.

Test the FeathersJS API using Postman

Now you can use Postman to test your API.

Send a POST request to http://localhost:3030/questions.

Create Questions

Send a GET request to http://localhost:3030/questions to retrieve the list of questions.

Get all Questions

Neat! Finally, update the git repository with the latest code. Do not forget to remove the “under construction” message from the index page.

git add .

git commit -m "Implement CRUD functionality for questions"

git push origin main

Your CircleCI build runs again and upon completion, your Heroku app is updated with the latest code. Great job!

Conclusion

In this tutorial, I have shown you how to set up a CI/CD pipeline for a FeathersJS API using GitHub, CircleCI, and Heroku.

By automating the process of releasing new features, the risk of human error on the production environment is greatly reduced. There is also a level of quality assurance added to the product as new features are deployed only if they behave as expected. That is, only if they pass the specified test cases.

This also makes for a more efficient software management process and happier developers. The repetitive, mundane aspects are automated so your team can focuses on problem-solving and creating. To learn more about the benefits of continuous integration and deployment, visit What is a CI/CD pipeline.

The entire codebase for this tutorial is available on GitHub.


Oluyemi is a tech enthusiast with a background in Telecommunication Engineering. With a keen interest in solving day-to-day problems encountered by users, he ventured into programming and has since directed his problem-solving skills at building software for both web and mobile. A full-stack software engineer with a passion for sharing knowledge, Oluyemi has published a good number of technical articles and blog posts on several blogs around the world. Being tech-savvy, his hobbies include trying out new programming languages and frameworks.