Continuous integration for Deno APIs
Fullstack Developer and Tech Author
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:
- Deno is secure by default. There is no file, network, or environment access, unless you explicitly enable it.
- It supports TypeScript out of the box.
- 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.
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:
- A
HTTP:OK
response (200
) is returned. - The response received is a JSON response.
- 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:
- Checks out the latest code from your GitHub repository.
- 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.
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.