As part of a development team provisioning software services you face a constant trade-off between speed and accuracy. New features should be made available in the shortest time possible with a high amount of accuracy, meaning no downtime and no bugs. Unforeseen downtime due to human error is a common risk of any manual integration processes you use to manage codebases. Preventing downtime can be one of the key drivers for teams taking on the challenge of automating integration processes.

Continuous integration (CI) occupies a sweet spot between speed and accuracy where high-quality features are made available as soon as possible. If a problem with the new code causes the build to fail, the customer is protected from receiving the contaminated build. When the build reaches users, code has been thoroughly tested. The result is that the customer experiences no downtime and no waiting for rollbacks.

In this article, you will learn how to use CircleCI to apply CI to a Deno project. In a companion tutorial, you can learn how to automatically deploy your Deno project to Heroku.

Deno is a simple, modern, and secure runtime for JavaScript and TypeScript. Using Deno gives you these advantages when you create a project:

  1. Deno is secure by default. There is no file, network, or environment access, unless you explicitly enable it.
  2. It supports TypeScript out of the box.
  3. It ships as a single executable file.

The sample project for this tutorial is an API built with Oak. This API has one endpoint which returns a list of quiz questions. A test case will be written for the endpoint using SuperOak.

Prerequisites

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

  • An up-to-date installation of Deno.

For repository management and continuous integration, you need:

  • A GitHub account. You can create one here.
  • A CircleCI account. You can create one here. To easily connect your GitHub projects, you can sign up with your GitHub account.

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.

Getting started

Create a new directory to hold all the project files:

mkdir deno_circle_ci

cd deno_circle_ci

To keep things simple, you can import a hard-coded array of questions in our project. Create a file named questions.ts and add this:

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

Setting up a Deno server

You will use the Oak middleware framework to set up your Deno server. It is a framework for Deno’s HTTP server, comparable to Koa and Express. To begin, create a file named server.ts and add this code to it:

import { Application, Router } from "https://deno.land/x/oak@v10.4.0/mod.ts";
import questions from "./questions.ts";

const app = new Application();
const port = 8000;

const router = new Router();

router.get("/", (context) => {
  context.response.type = "application/json";
  context.response.body = { questions };
});

app.addEventListener("error", (event) => {
  console.error(event.error);
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen({ port });
console.log(`Server is running on port ${port}`);

export default app;

This example uses the Application and Router modules imported from the Oak framework to create a new application that listens to requests on port 8000. It then declares a route that returns a JSON response containing the questions stored in questions.ts. Then it adds an event listener that will be triggered every time an error occurs, which is helpful if you need to debug when there is an error.

Running the application

You can run the application to see what has been done so far using this command:

deno run --allow-net server.ts

Go to http://localhost:8000/ to review the response.

API response

Writing tests for the application

Now that you have set up the server and run the app, you can write a test case for the API endpoint. Create a new file called server.test.ts and add this code to it:

import { superoak } from "https://deno.land/x/superoak@4.7.0/mod.ts";
import { delay } from "https://deno.land/std@0.202.0/async/delay.ts";
import app from "./server.ts";

Deno.test(
  "it should return a JSON response containing questions with status code 200",
  async () => {
    const request = await superoak(app);
    await request
      .get("/")
      .expect(200)
      .expect("Content-Type", /json/)
      .expect(/"questions":/);
  }
);

// Forcefully exit the Deno process once all tests are done.
Deno.test({
  name: "exit the process forcefully after all the tests are done\n",
  async fn() {
    await delay(3000);
    Deno.exit(0);
  },
  sanitizeExit: false,
});

The code in this example imports the superoak module and the application you created in server.ts. Then it declares a test case that creates a request using SuperOak and your application. It then makes a GET request to the application index route and makes these assertions:

  1. A HTTP:OK response (200) is returned.
  2. The response received is a JSON response.
  3. The JSON response received has a node named questions.

Running the test locally

Go to the terminal. From the root of the application, stop the server from running using CTRL + C. Then issue this command to run the test:

deno test --allow-net server.test.ts

The response should be:

Server is running on port 8000
running 2 tests from ./server.test.ts
it should return a JSON response containing questions with status code 200 ... ok (29ms)
exit the process forcefully after all the tests are done\n ...%

With your test cases in place, you can add the CircleCI configuration.

Adding CircleCI configuration

In your project root directory, create a folder named .circleci. Add a file called config.yml to that directory.

mkdir .circleci

touch .circleci/config.yml

In .circleci/config.yml add:

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

jobs:
  build-and-test:
    docker:
      - image: denoland/deno:1.37.1
    steps:
      - checkout
      - run: |
          deno test --allow-net server.test.ts
workflows:
  sample:
    jobs:
      - build-and-test

The code in this example first specifies the version of CircleCI pipeline process engine. Make sure to specify the latest version (2.1 at the time this article was written).

After specifying the CircleCI version, the code specifies a job named build-and-test. This job has two key blocks: docker and steps. The docker block specifies the images you need for the build process to run successfully. For this tutorial, use the official Deno Docker image.

The steps block:

  1. Checks out the latest code from your GitHub repository.
  2. Runs the tests in server.test.ts.

The build-and-test job is executed as specified in the workflows block.

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

Adding the project to CircleCI

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 deno_circle_ci project, click Set Up Project.

CircleCI will detect the config.yml file within the project. Click Use Existing Config and then Start Building. Your first build process will start running and complete successfully.

Click build-and-test to review the job steps and the status of each job.

CI Result

Conclusion

In this tutorial, I have shown you how to set up a continuous integration pipeline for a Deno application using GitHub and CircleCI. While your application was a simple one, with just one endpoint and one test case, you covered the key areas of pipeline configuration and feature testing.

CI builds on software development best practices in testing and version control to automate adding new features to software. CI removes the risk of human error causing downtime in the production environment. It also adds an additional level of quality control and assurance to the software being maintained. Give continuous integration a try and make code base bottlenecks a thing of the past for your team!

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.

Read more posts by Olususi Oluyemi