Deno is a new runtime for JavaScript and TypeScript. If this doesnât tell you much and you donât know what to expect, then take this statement as secondary introduction: Ryan Dahl, inventor of Node.js, released Deno in 2020 as answer to improve Node.js. However, Deno isnât Node.js, but a complete new runtime for JavaScript, but also TypeScript. Similar to Node.js, Deno can be used for server-side JavaScript, but aims to negate the mistakes that were made with Node.js. Itâs like Node.js 2.0 and only the future can tell whether people will adopt it as much as they did Node.js back in 2009.
Ryan Dahl, the inventor of Node (2009) and Deno (2020), released Deno as addition to the JavaScript ecosystem. When Ryan announced Deno the first time at a conference, he talked about the mistakes made in Node.js. Watching this conference talk (see exercises) is a lesson on humility, because Node.js has become indispensable for the JavaScript ecosystem, used by millions of people, and yet Ryan Dahl feels bad about decisions which were made back at the time. Now, Ryan Dahl wants to make things right with Deno by addressing the design flaws from Node. Deno is a brand new runtime for secure server-side JavaScript and TypeScript implemented by the V8 JavaScript engine, Rust and TypeScript.
Languages: JavaScript and TypeScript are first class languages at runtime in Deno. Whether you want to write your Deno application with one or the other is just one file extension away from you. While TypeScript gains continuous popularity, Deno with first class TypeScript support out of the box may be the appropriate answer to this trend.
Compatibility: Deno tries to be web compatible â which means that a Deno application should work in Deno and a browser alike. After all, itâs just an executable JavaScript (or TypeScript) file which shouldnât care much about its environment. While taking all this compatibility into account, Deno wants to be future-proof by using modern JavaScript and TypeScript features.
Security: Deno is secure by default. There is no file, network, or environment access happening, unless the developer allows it. This guards against the malicious usage of Deno scripts which will be most likely used as much as Node scripts out in the wild.
Standard Library: Deno comes with a standard library, which means applicationâs in Deno will be more opinionated than Node applications, because Deno comes with lots of in-house utility functions on top of JavaScript. In addition, Deno comes with several built-in tools that improve the development experience.
The following sections will show you all these bullet points in detail while implementing a small Deno application step by step from scratch. Afterward we will continue developing a real web application with Deno.
There are various ways to set up a Deno application. For you it depends on your operating system and on your toolchain for installing programs on your machine. For example, I am using Homebrew on MacOS to manage programs on my machine. For you it may be something else, so please take the appropriate command for your machine from this list which is taken from Denoâs website. This command should be executed in an integrated terminal or command line interface:
After you have installed Deno, you can verify its installation on the command line. Your version may be newer than mine, because in my case I installed the first released Deno version 1.0.0. However, the following sections will assume that you have the newest Deno version installed:
If you want to upgrade Denoâs version, you can use deno upgrade . In addition, try to execute the following remote Deno application via the command line to verify that Deno runs correctly on your machine:
This Deno application just outputs a text on your command line. However, it also shows you how a Deno application can be executed from a remote source by downloading and compiling it on the fly. If you have trouble to set up Deno on your machine, follow installation instructions on Denoâs official website .
Every time we learn something new as programmers, we start with the usual âHello Worldâ example. Letâs do this together for our first Deno application. On your command line, create a folder for your Deno project, navigate into this folder, and create a new file for this. Itâs up to you how you name the folder and file:
Now open the newly created index.js file, or whatever file name you have used, in your favorite editor or IDE. There, give it the following implementation for just outputting the following JavaScript string:
Afterward, start your Deno application on command line with the following statement. Depending on your chosen file name, it may be different from mine:
Your first Deno program outputs âHello Denoâ. Thatâs it. You have create a folder for your Deno project, created one JavaScript file for the implementation details, and ran this file via Deno on the command line. There is no other setup needed.
The following sections will evolve our first Deno application while we go through the different aspects of Deno. In this section, we will talk about permissions in Deno, because Deno is supposedly secure by default. We will see what this means in this example.
If you like to keep yourself informed about tech topics like I do, you may already know Hacker News . Usually you will find me every morning on this website to read about the latest news in tech. In addition, there exists an API for Hacker News that I like to use for my coding tutorials. We will use this API to fetch data for the sake of learning about data fetching in Deno and permissions. If you browse the Hacker News API , you may find the following URL to request stories about a certain topic:
We will use this URL in our Deno project â in our index.js file â for fetching Hacker News stories about JavaScript:
Next use the url in Denoâs native fetch function, which performs a HTTP GET request on this URL and which returns a JavaScript promise. You can resolve this promise by transforming it to JSON and output its result with a logging statement:
If you have been using JavaScript in frontend applications, you may have noticed that we are using the same fetch API â or at least the same interface which shares the same implementation details â that the browser API offers us for client-side applications. As mentioned earlier, Deno tries to be web compatible and any Deno application should work the same in the browser when executing its code. So Deno makes sure that APIs that are available in client-side JavaScript applications are also available in server-side Deno applications.
Now start Deno again on the command line:
You should receive the following error from Deno: âUncaught PermissionDenied: network access to â http://hn.algolia.com/api/v1/search?query=javascript â, run again with the âallow-net flagâ . This error appears because Deno is secure by default. If we operate in Denoâs domain, we can do lots of things without giving Deno any permissions. However, if we want to reach out of Denoâs area of ââresponsibility, we need to explicitly allow it. In this case, we need to allow network requests for the case of fetching data from a remote API:
After running the Deno application again, you should see an array of Hacker News stories printed on your command line. Each item in this array has lots of information, so for the sake of readability, letâs strip down the properties for each item (story). Afterward, the output should be more readable:
In this section you have learned that Deno is secure by default. Everything we want to access outside of Denoâs territory, which could be network access or file access, must be allowed by us. Otherwise Deno refuses to work.
Before you have already seen how fetch works in Deno. The native fetch API is commonly known in browser applications which run on the client-side. So instead of having a new API for Deno, we can use exactly the same interface as in our client-side applications. Thus we donât need to rethink our approach when using Deno.
Deno tries to keep up with modern JavaScript features, whether used on the client or server, too. Letâs take async/await for example, which has only been available in newer Node.js versions, which comes in Deno by default. And not only async/await, but also top level await without async, which hasnât been available in Node.js for a long time, is available to us:
Instead of a promiseâs regular then and catch blocks, we can use await to run code synchronously. All code after the await statement only executes after the promise has been resolved. If this implementation would run in a function, it would have to declare the function as async though. Async/await and top level await are available in Deno out of the box.
Deno comes with a set of utility functions which are known as Denoâs standard library (short: Deno std). Rather than having everything imported from external libraries, Deno tries to be more opinionated by offering several in-house solutions. We will try one of these standard library solutions for setting up a web server:
There are a few things happening here. First, we do a named import from the standard library with an absolute path. In Deno, all library imports, whether they are from the standard library or from a third-party library, are done with an absolute path which point to a dedicated file. You can convince yourself that this http library with its server file exports a named served function.
The serve function creates a web server for us which is accessible via a defined port . JavaScript for await ⦠of is used to iterate through every incoming request for this server. And for every request, the server returns the same text in a body.
Run your Deno application again and navigate to http://localhost:8000 in your browser. Because we are using network traffic again, we need to give the permission to use it:
Both URLs, http://localhost:8000 and http://localhost:8000/ with a trailing slash, work alike in the browser. Whenever we open one of the URLs in the browser, a HTTP GET request is made to the Deno application and this request returns a HTTP response with the âHello Denoâ body which then will be displayed in the browser.
Letâs extend the example with our previous code. Instead of sending a hard coded text from the server (Deno) back to the client (browser), we will fetch top JavaScript stories from Hacker News and send them to the client:
After starting the Deno application again, we should see the result from the fetch request printed in the browser as JSON. Previously we have seen this result only on the command line, but now with a web server at our hands, we can send the result to a client (browser).
Eventually Denoâs standard library will not be enough to create Deno applications. Thatâs where third party libraries (also called external libraries or just libraries) come into play. If you check the result from the last section in the browser again, you may notice that the createdAt has a human unreadable format. We will use the date-fns library to make it readable for us:
Libraries in Deno are imported with an absolute path directly from the web. Again you could open the URL in the browser, read the source code there, and check whether it actually exports a default function which is the format function here:
The format function takes two mandatory arguments: a date and a pattern for the formatted date. The date we receive from the Hacker News API is a unix timestamp in seconds; which is why we need to convert it first to milliseconds before creating a JavaScript date from it. The pattern we provide as second argument to the function makes the date human readable.
After starting your Deno application again, you can see that it downloads the format function from the library plus all additional dependencies. Since we have used a direct URL to the function, only this part of the library gets downloaded. Try to include the whole library path where format is only one of many named exports (curly braces). You will see that the whole library will be downloaded:
Whenever you start Deno again, the library will be cached and thus doesnât need to be downloaded again. Every time Deno starts again it checks all its imports, downloads them, and bundles them to one executable file. Importing libraries in Deno is hugely inspired by Go . Itâs not necessary to keep a list of dependencies in a file (e.g. package.json from Node.js) and itâs not necessary to have all modules visible in your project (e.g. node_modules from Node.js).
You have learned that imports for Denoâs standard library or third party libraries are performed with an absolute path. This approach is inspired by the Go programming language, because it doesnât leave much room for confusion. In contrast to std or libraries, if you want to import files from within your projectâs source code, letâs say because you have more than one file for your Deno application, you can import them with a relative path.
Letâs see how this works: Start by creating a second file in your project with the name stories.js which should be placed next to your index.js file. In this stories.js file, put the following implementation which is essentially the mapping that we did previously in the other file:
Since we are doing a named export in the stories.js file, we can do a named import in the index.js file and use the function later in our code:
Thatâs it for exporting and importing files in Deno. Instead of using absolute paths like we have done before for Denoâs standard library or external libraries, we are using a relative path to import whatâs necessary. Itâs also worth to note that regardless of absolute or relative path, we must always include the file extension, because we donât want to leave any room for ambiguity.
Testing shouldnât be an afterthought in programming and it isnât in Deno. Getting the confidence that your applications works makes testing indispensable. Thatâs why testing is baked into Deno. We will see how it works by writing our first unit test together. First, create a new stories.test.js file next to your other files. Then give it the following implementation:
Deno offers us a test function to define our test with a name/description and the actual test function. Itâs up to us to implement the test in the functionâs body. We already import the function that we want to test (here mapStory ) which essentially just takes an array of stories and returns a new array of stories with less properties and a formatted date. All we need to do is to define a list of stories which be used for mapStory and an expected list of stories which we assume as output of this function:
Denoâs standard library gives us the assertEquals function for the assertion of two values. The first value is the output of our function that we want to test and the second value the expected output. If both match, the test should turn green. If they donât match, the test should fail and turn red. On the command line, run all your tests with the following command:
The test turns green. With deno test , all files with the naming pattern test.{js,ts,jsx,tsx} are picked up. You can also test only specific files with deno test <file_name> which would be deno test stories.test.js in our case.
Deno supports both JavaScript and TypeScript as first class languages. Thatâs why itâs important to always include the file extension for file imports â regardless of whether these files are relative imports from your Deno project or absolute imports from Denoâs standard library or third-party libraries.
Since Deno supports TypeScript as first class citizen, wen can just rename the stories.js file to stories.ts . You will notice that you need to adjust all the imports â in index.js and stories.test.js â pointing to this file, because the file extension changes from .js to .ts .
The stories.ts file with all the implementation details expects types now. We will provide interfaces for the input and output of the function with Story and FormattedStory :
Now this function is fully typed. You can continue the transition from JavaScript to TypeScript on your own by renaming the stories.test.js file to stories.test.ts and the index.js file to index.ts . Not all of these new TypeScript files require to add types or interfaces, because most types are inferred automatically.
If you want to start your Deno application in TypeScript again, note that you have to adjust the file extension for the Deno script:
Deno comes with a default TypeScript configuration. If you want to customize it, you can add a custom tsconfig.json file (see exercises). After all, since TypeScript is as much first class citizen as JavaScript, itâs up to you which file extension you take for your future Deno project.
Environment variables are great for hiding sensitive information about your Deno application. This can be API keys, passwords, or other data which shouldnât be visible to others. Thatâs why there exists the .env file, which you would have to create, to hide sensitive information. We will create this file and pass it the following information for the port of our server application:
In the index.ts file, we can use this environment variable with a third party library:
The utility function returns an object with all the key/value pairs from the .env file. We have to parse value for the 'PORT' key to a number, because it is available as string in the object. Now the information isnât exposed in the source code anymore, but only available in the environment variables file.
Once you start your Deno application again, you should see another permission error showing up on the command line: âUncaught PermissionDenied: read access to â/Users/mydspr/Developer/Repos/deno-exampleâ, run again with the âallow-read flagâ . You can allow the access on environment variables with another permission flag:
Important: The .env file shouldnât be shared in a public repository where everybody can see it. If you make your source code public, for example on GitHub, consider adding the .env file to a .gitignore file.
After all, the port of a server application may be not the best example for sensitive data. We used the port for the sake of learning about environment variables. However, once you deal with more features ion your Deno application, you may end up with information used in your source code that shouldnât be visible to others.
The last sections have introduced you to all the basics in Deno. Deno will be used in the same areas as Node.js, from small script to full-blown server application, yet with a much more improved set of defaults. Itâs secure by default with its permissions, compatible with lots of client-side APIs, fitted with modern features like top level await, and utilized with JavaScript or TypeScript.