Automate the deployment of FeathersJS apps to Heroku
Fullstack Developer and Tech Author
This is one of a two-part series. You can also learn how to automate testing of FeathersJS apps.
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 like NPM or Yarn
- The FeathersJS CLI
Install the FeathersJS CLI 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.
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.
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.
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.
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:
- 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.
- 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.
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
andPUT /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
.
Send a GET
request to http://localhost:3030/questions
to retrieve the list of 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.