Is Deno.js Going to Overtake Node.js?


What is Deno?

Deno is a safe and stable runtime environment for JavaScript and TypeScript baked on the V8 engine. It’s written in Rust. The creators of Deno had the intention of taking full advantage of all the latest features of the JavaScript language. For example, its API supports the Promise object and uses the ES module as a default module system. They also designed to be self-sufficient — no external dependencies required. Safety is a major design priority in Deno. All-access operations to the disc or online must be explicitly issued in the script.

Deno vs Node.js

Deno creators have every intention to provide a strong alternative to Node.js. Hence, it’s no surprise that even though they have a lot in common, there are a couple of major differences between these two. 

“Deno”  was basically just an anagram for node. JS and indeed they know as we can see here on their official page download that Deno is a secure runtime for JavaScript and Typescript if we have a look at nodejs.org we see that’s not too far away from what we find their node.JS is a JavaScript runtime built on chrome’s V8 JavaScript engine and indeed there now in the end is a new JavaScript runtime developed by the inventor of node.

Let’s take a look at a simple side-by-side comparison from Terkwaz Business Solutions: – 

DENO NODE
Uses V8 Uses V8
Written in RUST and TypeScript  Written in C++ and JavaScript 
Run in the sandbox with limited access – explicit command issue in the script required. The question of access is limited to the specific access rights of a user that runs the script.
Decentralized modules – loaded from an URL. NPM 
ES Module CommonJs 
The API and the standard library takes full advantage of ES and Promise. The API and standard library based on callbacks.


Let’s practice!

As with any piece of technology, practice beats the theory. But before we get to the best part with Terkwaz Business Solutions, let’s first install the Deno platform and quickly go over the app structure.

Photo by Brad Barmore on Unsplash

Installation

All you get is a single executable file without any extra dependencies. You can do it via an OS package manager. For example, for Homebrew, the command will be brew install deno. You can also get the binary files straight from the official Deno repository.

https://github.com/denoland/deno/releases

App Structure

One thing that really stands out from the beginning is the fact that you can write your app directly in TypeScript. You don’t need to compile to JavaScript yourself. In addition, a method for compiling TypeScript using Rust is also currently in the planning phase.

Deno’s creators made the bold decision to get rid of a centralized package manager, such as npm or NuGet. Instead, the code is loaded directly from the URL. Once you run a script, the packages are downloaded from a specified location and then cached.

import { Application, Router } from “https://deno.land/x/[email protected]/mod.ts";

What’s also interesting is that you can use various versions of a given library in one file. All you need to do is import it and add aliases.

import { Application as AppOld } from “https://deno.land/x/[email protected]/mod.ts";

Simple module-based Application & Unit Testing

Let’s write a simple script that takes numbers as parameters. Then, we’re going to use a module to add all integers. The result is going to be output to the console.

We’re going to use built-in tools to write a simple test for the module. To simplify things, we can convert all parameters to integers.

Let’s make three files.

  • index.ts – the main script of the application,
  • mathService.ts – a class that has a public method that multiplies a number by two,
  • mathService.test.ts – a test for the multiply class.

export class MathService { 
sum(numbers: number[]) {
return numbers.reduce((value, number) => Number.isInteger(number) ? value + number : value, 0)
}
}

Deno has a built-in test runner and ready-made asserts. You can view them here: https://deno.land/std/testing

At this moment, there is no information on creating stubs.

import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; 
import { MathService } from './mathService.ts';

Deno.test('MathService', () => {
// given
const sut = new MathService();

// when
const result = sut.sum([1, 3, 5]);

// then
assertEquals(result, 9);
})

We can launch the tests directly from the console with the deno test command.

$ deno test

running 1 tests
test MathService ... ok (3ms)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (4ms)

All there’s left to do is to write the main script. It’s worth it to notice that Deno gives us direct access to the Deno namespace in all the scripts.

import { MathService } from './mathService.ts'; 

const mathService = new MathService();
const numbers: number[] = [];

Deno.args.forEach(number => numbers.push(parseInt(number, 10)));

console.log(`Result: ${mathService.sum(numbers)}`);

To run the script, we go to the console and run the command:

$ deno run index.ts 2 7 6

Simple API

Simple shell-based scripts are hardly the extent of Deno’s ability. Ever since Deno was released, developers have been adding more and more 3rd party libraries. Even though (much like it was the case with Node at the beginning), some of them are quite unpolished.

Let’s now use the capabilities of the denotrain library to create a simple API. This library is clearly inspired by Node’s Express.js so the code may seem familiar. To run a basic API, we only need one index.ts file and 8 lines of code.

import { Application, Router } from "https://deno.land/x/[email protected]/mod.ts"; 

const app = new Application();
const routes = new Router();

routes.get("/status",() => {
return 'ok';
});

app.use("/api",routes);

await app.run();

It’s a good moment to mention that Deno supports Top-Level Await (last line of the code above). It’s a different story if this is the right solution, but it is sufficient for this simple example.

We can now run the script with the command below (granting access to the network in the process):

$ deno run --allow-net index.ts

Serving on http://0.0.0.0:3000/


In Tutorial What are we building?

In this tutorial, we will be developing Newzzer: a simple CLI app to show us the latest news using Deno. During this process, we will learn and use different features of Deno.
The app will have following 2 major features :-

  1. Search news by query
  2. Search news by category

Prerequisites

  1. Deno
    You can refer to download instructions from https://deno.land/#installation. The installation is pretty straight forward for all major platforms. You can verify the installation by running deno --version . At the time of writing, the latest release was Deno v1.0.3 . You can also run deno --help to view additional commands.
  2. NewsAPI API key
    We will be using NewsAPI’s API service to get news. We can get the API key for free by creating an account on https://newsapi.org/account
  3. Basic knowledge of JavaScript/ TypeScript.
  4. Visual Studio Code (Optional, if you like IntelliSense and auto-completion)

Note:- I will be using TypeScript throughout this article. If you are not comfortable with TypeScript, you can remove the types and follow through.

Getting Started

Let’s kick things off with the official ‘Welcome’ program. In Deno, we can directly run a program from the file URL. Just run deno run https://deno.land/std/examples/welcome.ts in your terminal.

Bravo! You just ran your first Deno program. The first time when the command is run, the file is downloaded, compiled, and run. The next time when the same file is run, it is neither downloaded nor compiled. This is due to the fact that Deno caches the dependency and the compiled files.

What if we want to refresh the cache and force it to use the new version?

 --reload to the rescue, you can run the file using this flag to reload the source code cache.

Let’s go to the code URL and sneak what’s inside the file.

It’s a simple console.log , but what’s with the styling of the page? The Deno website can detect that the traffic is coming from a web browser and renders the file in a styled way, but when accessed from a non browser environment, it returns back the raw document. Don’t believe me? Let try to download the file using curl and check the content.

File structure

newzzer
├── api.ts
├── deps.ts
├── error.ts
├── mod.ts
└── types.d.ts

We will be creating 5 files as depicted above with each file serving a specific purpose.

mod.ts : This is the entry point of our app. According to conventions in famous Deno programs and standard libraries, the name of main file is generally mod.ts, but it can be named anything.

types.d.ts : This file will house our custom interface definitions.

deps.ts : This file will store all our imports from external dependencies and will serve as an centralised store to import all the dependencies from.

api.ts : This file contains the logic for interaction with NewsAPI.

error.ts : This file will contain the logic for error handling in our app.

If you are coming from a NodeJS background, you’d notice that there is no package.json file.

Deno has no centralised/ official package manager

This is an intentional design choice of Deno. Deno uses URLs to import external modules just like a browser would. Because there is no official package manager, anyone can host their module on internet and can be made available for download from the URL.

Note: If you are using VSCode, I’d suggest you to download vscode-denoextension to help with auto completion, intellisense and the formatting.


Type Definitions

Firstly, let’s define our type definition in types.d.ts. We will create type definitions for the NewsAPI response using the example provided on their site as reference. We will also create an interface for the config file, which will host the secret-key required to access the NewsAPI.

// types.d.ts
export interface IArticle {
source: Object;
author: string;
title: string;
description: string;
url: string;
urlToImage: string;
publishedAt: string;
content: string;
}

export interface IConfigFile {  
apiKey: string;
}

Getting the dependencies

Let’s first define our objectives once again. The app will:-

  1. Allow us to set our NewsAPI api-key in config file (using --config flag)
  2. Show top 10 latest news
  3. Apply category filter (using --category or -c flag)
  4. Apply query filter (using --query or -q flag)
  5. Display help text (using --help or -h flag)

Let’s define all out dependencies first and get them out of the way:-

  1. std/flags : Help to parse command line arguments
  2. std/fs : Help to perform file operation
  3. std/fmt : Coloured console.log (Adding colors to our output)

As you can see all the dependencies are in formstd/* which means they are Deno standard modules. The Deno website maintains the list of all the modules in the standard library. You can view the full list here.

You can also view a list of third-party modules created by the community here. In this tutorial I’ll be restricting myself purely to the standard modules.

Let’s import the modules in deps.ts

// deps.ts
export { parse, Args } from "https://deno.land/std/flags/mod.ts";
export { readJsonSync } from "https://deno.land/std/fs/read_json.ts";
export { writeJsonSync } from "https://deno.land/std/fs/write_json.ts";
export { existsSync } from "https://deno.land/std/fs/exists.ts";
export {
red,
green,
bold,
cyan,
yellow,
magenta,
} from "https://deno.land/std/fmt/colors.ts";

Behind the scenes

Deno uses ECMAScript Modules(ESM) standard. If you are coming from NodeJS, you must be familiar with CommonJS module system.

// ESM
import abc from "abc";

// CommonJS
const abc = require("abc");

When NodeJS was built, there was no standard for browser modules and NodeJS decided to adopt the CommonJS way. Later ECMAScript standardised ESM modules as the module system for browsers. The support for ESM modules is now built into major modern web browser. For more info on ES modules check out flaviocope’s article.

Why did Deno and ECMAScript divert from the CommonJS way? Well, it had issues — majorly complex module resolution. I’ll try my best to explain it. Consider an import inside NodeJS as, const abc = require("abc") .
While resolving the dependency abc, NodeJS has to check

  • Is abc a standard NodeJS module (similar to fs)
  • Is abc an external NodeJS module? (checks for it in node_modules)
  • Is abc a file from the project? (checks for file/folder name in project)
  • Is abc referring to a file abc.js or is it referring to a file abc/index.js ? (Remember, you don’t have to explicitly specify index.js. NodeJS infers it for you)

These features, while being useful, makes the module resolution a complicated task. ESM standard saves some of the complexity by only allowing absolute and relative URL.  Learn more with a free Technology Consultation! 


Basic help message

Every CLI is incomplete without a help message accessible through -h or --help or when some invalid config is passed. Let’s create the basic help message in error.ts

//error.ts 

// Importing colors
import { red, bold, cyan } from "./deps.ts";

// Shows help text, error message(if present) and exits the programexport const displayHelpAndQuit = (error?: string): void => {  
if (!error) {
} else if (error === "INVALID_KEY") {
console.log(
bold(red(`Error: Invalid API key. Use --config flag to set key`)),
);
} else console.log(bold(red(`Error: ${error}`))); console.log(`Usage: newzzer [filters]\n`);
console.log(`Optional flags:`);
console.log(` ${bold("-h, --help")}\t\t Shows this help message and exits`);
console.log(`
${bold("-q, --query")}\t\t Find news related to a specific keyword`,
);
console.log(
` ${
bold(
"-c, --category",
)
}\t Find news in a valid category\n\t\t\t The valid categories are: business, entertainment, general, health, science, sports, technology`,
);
console.log(
` ${
bold(
"--config <API_KEY>",
)
}\t Set API key for news API. The key can be recieved from ${ cyan(
`https://newsapi.org/register`,
)
}`,
);
// Exits the program
Deno.exit();
};

displayHelpAndQuit function accept string error as argument. If the error is passed, it shows the error and then the help message, else it just shows the help message. The help message contains information about the supported flags and their purpose.

Adding colours to the logs is pretty easy and straight forward, thanks to std/fmt/colors standard module we imported in deps.ts . To create a red bold text with green background, the code is as simple as

console.log(bgGreen(bold(red("Error"))));

At the end of displayHelpAndQuit , we exit the Deno process using Deno.exit()

Now that we’ve completed the help message part, let’s integrate it with our entrypoint, i.e. mod.ts and test it.

// ***************
// IMPORTS
// ***************
import { parse, green, bold } from "./deps.ts";
import { displayHelpAndQuit } from "./error.ts";
// ***************
// FUNCTIONS
// ***************
const displayBanner = (): void => {
// Clears the terminal
console.clear();
console.log(bold("---------------"));
console.log( bold( green(` Newzzer`), ), ); console.log(bold("---------------"));
console.log( bold( green( `\nFind your quick news byte at your terminal. Powered by News API\n`, ), ), );}; // ***************
// Main method
// ***************
if (import.meta.main) {
const { args } = Deno;
const parsedArgs = parse(args);
displayBanner();
if (args.length === 0 || parsedArgs.h || parsedArgs.help) { displayHelpAndQuit();
} else displayHelpAndQuit("Invalid argument");
}

import.meta.main checks if the current file has been executed as the main input and not imported and executed inside another file. It is quite similar to if __name__ == "__main__" in python and require.main === module in NodeJS.
args in Deno namespace returns the argument vector. parse is used to parse these flags and return key value pairs of flag in a Map like structure.

// test.tsimport { parse } from "https://deno.land/std/flags/mod.ts";
const {args} = Deno;
console.log(args);
console.log(parse(args));// deno run test.ts -a --b=123[ "-a", "--b=123" ]
{ _: [], a: true, b: 123 }

Now let’s run our file and check the output

deno run mod.ts --help should show the help message

Now let’s try with an invalid flag deno run mod.ts --blah

Configuring the API key

We want user to configure their own API key from NewsAPI to get the news. For this, we will save the API key in a file .newzzer.json in the home directory of the user.

But every operating system has a different home directory, how do we get a path to the home directory? We can get it from the “HOME” environment variable inside the terminal. Deno provides a method Deno.env.get to get the environment variables.

Let’s edit our mod.ts and include the logic

// ***************
// IMPORTS
// ***************
import {
parse,
green,
bold,
Args,
existsSync,
writeJsonSync,
readJsonSync,
} from "./deps.ts";
import { displayHelpAndQuit } from "./error.ts";
import { IArticle, IConfigFile } from "./types.d.ts";// ***************
// FUNCTIONS
// ***************

const setApiKey = (parsedArgs: Args): void => {  
// Get home directory address
let homeEnv: string | undefined = Deno.env.get("HOME");
let home: string = "";
if (typeof homeEnv === "string") home = homeEnv;
let configFilePath: string = `${home}/.newzzer.json`;
// Check if api-key is provided
if (typeof parsedArgs.config === "string") {
// If the file is not present, then create file
if (!existsSync(configFilePath)) { Deno.createSync(configFilePath);
}
// Write apiKey in the file
writeJsonSync(configFilePath, { apiKey: parsedArgs.config }); console.log(`${green(bold("Success"))} ApiKey set Successfully`); displayHelpAndQuit();
}
// Handling if apiKey is not present after --config
else displayHelpAndQuit("Config flag should be followed by apiKey");
};

const getApiKey = (): any => {  
// Get home directory address
let homeEnv: string | undefined = Deno.env.get("HOME");
let home: string = "";
if (typeof homeEnv === "string") home = homeEnv;
let configFilePath: string = `${home}/.newzzer.json`;
try {
// try to read ~/.newzzer.json
let file = readJsonSync(configFilePath);
if (typeof file === "object" && file !== null) {
let configFile = file as IConfigFile;
if (configFile.apiKey) return configFile.apiKey;
// If apiKey not present in file show error
else displayHelpAndQuit("apiKey not found in the config file ");
}
} catch (err) {
// if file is not present, show error message and quit displayHelpAndQuit("Config file not present. Use --config to set apiKey");
}
};

const displayBanner = (): void => {  
// Clears the terminal
console.clear();
console.log(bold("---------------"));
console.log(
bold(
green(`
Newzzer
`),
),
);
console.log(bold("---------------"));
console.log(
bold(
green(
`\nFind your quick news byte at your terminal. Powered by News API\n`,
),
),
);
};
// ***************
// Main method
// ***************
if (import.meta.main) { const { args } = Deno; const parsedArgs = parse(args); displayBanner();
// If option to set API Key i.e. --config flag is passed if (parsedArgs.config) setApiKey(parsedArgs);
// otherwise Check for API key
let apiKey: string = getApiKey();
console.log(`Found API key: ${apiKey}`);
if (args.length === 0 || parsedArgs.h || parsedArgs.help) { displayHelpAndQuit();
} else displayHelpAndQuit("Invalid argument");
}

Get the API key from NewsAPI and let’s set it to our config file.

deno run mod.ts --config "API KEY HERE"

Looks like there was an error. We are unable to access the environment variables, but why?

Behind the scenes

Remember one of the Deno features which was mentioned in the starting?

Security — Code runs in a Sandbox by default

Security is one of the major USP of Deno over NodeJS. In Deno, your code runs in a sandbox and you have no access to:-

  • File System
  • Environment Variable
  • Network Access
  • High resolution time measurement
  • Loading Plugin

These permission has to be explicitly given while running the code using flags. You can find all the flags from deno run --help

Let’s give the permission and set our config key using deno run --allow-read --allow-write --allow-env mod.ts --config "API KEY HERE"

We can check if the API key is set using deno run --allow-read --allow-write --allow-env mod.ts

Connecting the API

Now let’s add the juicy part. Getting the news from the API and displaying it. All the logic of the API will be nested in app.ts

// api.ts
import { IArticle } from "./types.d.ts";
class Api {
// private property readonly #baseURL: string = "https://newsapi.org/v2/top-headlines";
#apiKey: string = "";
// set API key
constructor(apikey: string) {
this.#apiKey = apikey;
}

getNews = async (    
category: string | undefined,
query: string | undefined,
): Promise<IArticle[] | string> => {
let additional: string = "";
let country: string = "IN";
// Use US for USA , refer documentation for the complete list
if (category) additional += `&category=${category}`;
if (query) additional += `&q=${encodeURI(query)}`;
try {
const rawResult = await fetch(
`${this.#baseURL}?language=en&pageSize=10${additional}&apiKey=${this.#apiKey}&sortBy=popularity&country=${country}`,
);
const result = await rawResult.json();
if (result.status === "error")
return "INVALID_KEY";
let news: IArticle[] = result.articles;
return news;
} catch (err) {
return "Cannot connect to server. Please check your internet conection";
}
};
}

export default Api;

Deno tries to adhere to W3C standards as much as possible. As a result, Deno comes with a support for fetch API to interact with the web servers.

Now let’s use the API in mod.ts . This is the final file along with some error handling.

Now let’s run our app, don’t forget to add --allow-net to allow internet access.

deno run --allow-net --allow-read --allow-write --allow-env run mod.ts --category technology

Yayyy! We built our first fully functional CLI app together.

Deno Summary

Photo by Warren Wong on Unsplash

As you learn more and more about Deno, the one question you probably have in mind is whether Deno can replace or at least be a strong alternative to Node.js. Node’s doing great and I can’t think of a good reason to give up on it. At this point in time, the best way of thinking about Deno is as an interesting alternative rather than a one-to-one replacement.

Even though the 1.0 version got a proper release, I’d still be wary of writing production code in Deno. I just don’t think that it’s mature enough. Another challenge is to properly protect our app from external modules. We are bound to come across challenges such as temporary unavailability of a package. On the other hand, a very interesting aspect of Deno is the ability to write shell scripts in TypeScript.

It’s definitely worth it to keep exploring Deno because in the future it might just become an important player in the currently heavily monopolized field of JS runtime environments. Especially that the barrier to entry for Node.js developers is practically non-existent. Finally, do not forget to follow Terkwaz Business Solutions for more useful information in the software development industry, and you can check our daily posts out on linked-In here

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest

You may also like