deno tutorial by https://fediverse.org/aralroca

Introduction

Node.js was writtern initially by Ryan Dahl on 2009 (in C++).

Ryan left Node.js in 2012, as at this point he felt he had more or less fulfilled his goals.

His goals are now different.

After realizing that there were some design errors impossible to fix in Node.js, he decided to create another JavaScript (also TypeScript) runtime built with V8: Deno (in Rust).

Deno 1.0.0 will be finally released on 13th May 2020.

Installing Deno

There are different ways to install Deno: Using curl, iwr, Homebrew, Chocolatey…

See how to install it here .

Deno is a single binary executable, it has no external dependencies .

VSCodium

In case that you are using VSCodium, I recommend to install this plugin to ease working with Deno:

Simple “Hello World”

deno run https://deno.land/std/examples/welcome.ts
Download https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕

For a simple “Hello world” in Deno, we just need to create a file .js or .ts, and execute it with deno run [file].

In case of .ts, it will compile + execute, meanwhile for .js, the file will be executed directly:

// example.ts file
console.log("🦕 Hello from Deno 🖐 🦕");

And in the shell:

::

$ deno run example.ts

Compile ..javascript_runtimes/deno/tutorials/aral_roca/example.ts
🦕 Hello from Deno 🖐 🦕

The tsconfig.json file is optional because in Deno there are some TypeScript defaults.

To apply the tsconfig.json we should use deno run -c tsconfig.json [file].

By the way, Deno uses web standards where possible .

It’s possible to use window, fetch, Worker…

Our code should be compatible with both Deno and the browser .

Serve an index.html

Deno has his own standard library https://deno.land/std/ so to use their modules we can import it directly from the URL.

One of its goals is shipping only a single executable with minimal linkage.

This way it’s only necessary to import the URL to their projects, or execute directly with deno run https:// … in case of CLIs.

In order to create a http server and serve an index.html we are going to use this module: https://deno.land/std/http/ .

We are going to create two files: server.ts and index.html.

index.html

 1<!DOCTYPE html>
 2<html lang="en">
 3  <head>
 4    <meta name="viewport" content="width=device-width, initial-scale=1" />
 5    <meta charset="utf-8" />
 6    <title>Example using Deno</title>
 7  </head>
 8  <body>
 9    index.html served correctly
10  </body>
11</html>

server.ts

 1import { listenAndServe } from "https://deno.land/std/http/server.ts";
 2
 3listenAndServe({ port: 3000 }, async (req) => {
 4  if (req.method === "GET" && req.url === "/") {
 5    req.respond({
 6      status: 200,
 7      headers: new Headers({
 8        "content-type": "text/html",
 9      }),
10      body: await Deno.open("./index.html"),
11    });
12  }
13});

We can use ESmodules by default instead of Common.js, indicating the file extension always at the end.

Moreover, it supports the latest features as async-await.

Also, we don’t need to worry about formatting anymore. Instead of using tools as Prettier, we can format the files with deno fmt command.

The first time deno run server.ts runs, we’ll see two differences with respect to the “Hello World” example:

  • It downloads all the dependencies from http module.

  • Instead of using yarn or npm install, it should install all the needed dependencies before running the project.

  • This happens only the first time, since it’s cached. To clean the cache you can use the –reload command .

  • It throws an error Uncaught PermissionDenied: network access to “127.0.0.1:3000”, Deno is secure by default. This means that we can’t access to the net or read a file (index.html).

This is one of the big improvements over Node .

In Node any CLI library could do many things without our consent.

With Deno it’s possible, for example, to allow reading access only in one folder:

deno --allow-read=/etc.

To see all permission flags, run deno run -h

deno run –allow-net –allow-read server.ts

$ deno run --allow-net --allow-read server.ts
Compile file:///home/pvergain/tutos/tuto_javascript/javascript_runtimes/deno/tutorials/aral_roca/server.ts
Download https://deno.land/std/http/server.ts
Download https://deno.land/std/encoding/utf8.ts
Download https://deno.land/std/io/bufio.ts
Download https://deno.land/std/testing/asserts.ts
Download https://deno.land/std/async/mod.ts
Download https://deno.land/std/http/_io.ts
Download https://deno.land/std/io/util.ts
Download https://deno.land/std/path/mod.ts
Download https://deno.land/std/path/win32.ts
Download https://deno.land/std/path/posix.ts
Download https://deno.land/std/path/common.ts
Download https://deno.land/std/path/separator.ts
Download https://deno.land/std/path/interface.ts
Download https://deno.land/std/path/glob.ts
Download https://deno.land/std/path/_constants.ts
Download https://deno.land/std/path/_util.ts
Download https://deno.land/std/fmt/colors.ts
Download https://deno.land/std/testing/diff.ts
Download https://deno.land/std/path/_globrex.ts
Download https://deno.land/std/async/deferred.ts
Download https://deno.land/std/async/delay.ts
Download https://deno.land/std/async/mux_async_iterator.ts
Download https://deno.land/std/textproto/mod.ts
Download https://deno.land/std/http/http_status.ts
Download https://deno.land/std/bytes/mod.ts
../../../../../_images/example_server.png

Using WebSockets

WebSockets, UUID, and other essentials in Node are not part of the core.

This means that we need to use third-party libraries to use it.

Yet, you can use WebSockets and UUID among many others by using Deno standard library. In other words, you don’t need to worry about maintenance, because now it will be always maintained.

To continue implementing our simple chat app, let’s create a new file chat.ts with:

 1 import {
 2   WebSocket,
 3   isWebSocketCloseEvent,
 4 } from "https://deno.land/std/ws/mod.ts";
 5 import { v4 } from "https://deno.land/std/uuid/mod.ts";
 6
 7 const users = new Map<string, WebSocket>();
 8
 9 function broadcast(message: string, senderId?: string): void {
10   if(!message) return
11   for (const user of users.values()) {
12     user.send(senderId ? `[${senderId}]: ${message}` : message);
13   }
14 }
15
16 export async function chat(ws: WebSocket): Promise<void> {
17   const userId = v4.generate();
18
19   // Register user connection
20   users.set(userId, ws);
21   broadcast(`> User with the id ${userId} is connected`);
22
23   // Wait for new messages
24   for await (const event of ws) {
25     const message = typeof event === 'string' ? event : ''
26
27     broadcast(message, userId);
28
29     // Unregister user conection
30     if (!message && isWebSocketCloseEvent(event)) {
31       users.delete(userId);
32       broadcast(`> User with the id ${userId} is disconnected`);
33       break;
34     }
35   }
36 }

Now, register an endpoint /ws to expose the chat on server.ts

 1 import { listenAndServe } from "https://deno.land/std/http/server.ts";
 2 import { acceptWebSocket, acceptable } from "https://deno.land/std/ws/mod.ts";
 3 import { chat } from "./chat.ts";
 4
 5 listenAndServe({ port: 3000 }, async (req) => {
 6   if (req.method === "GET" && req.url === "/") {
 7     req.respond({
 8       status: 200,
 9       headers: new Headers({
10         "content-type": "text/html",
11       }),
12       body: await Deno.open("./index.html"),
13     });
14   }
15
16   // WebSockets Chat
17   if (req.method === "GET" && req.url === "/ws") {
18     if (acceptable(req)) {
19       acceptWebSocket({
20         conn: req.conn,
21         bufReader: req.r,
22         bufWriter: req.w,
23         headers: req.headers,
24       }).then(chat);
25     }
26   }
27 });
28
29 console.log("Server running on localhost:3000");

To implement our client-side part, we are going to choose Preact to be able to use modules directly without the need of npm, babel and webpack, as we saw on the previous article.

 1 <!DOCTYPE html>
 2 <html lang="en">
 3   <head>
 4     <meta charset="UTF-8" />
 5     <title>Chat using Deno</title>
 6   </head>
 7   <body>
 8     <div id="app" />
 9     <script type="module">
10       import {
11         html,
12         render,
13         useEffect,
14         useState,
15       } from 'https://unpkg.com/htm/preact/standalone.module.js'
16
17       let ws
18
19       function Chat() {
20         // Messages
21         const [messages, setMessages] = useState([])
22         const onReceiveMessage = ({ data }) => setMessages((m) => [...m, data])
23         const onSendMessage = (e) => {
24           const msg = e.target[0].value
25
26           e.preventDefault()
27           ws.send(msg)
28           e.target[0].value = ''
29         }
30
31         // Websocket connection + events
32         useEffect(() => {
33           if (ws) ws.close()
34           ws = new WebSocket(`ws://${window.location.host}/ws`)
35           ws.addEventListener('message', onReceiveMessage)
36
37           return () => {
38             ws.removeEventListener('message', onReceiveMessage)
39           }
40         }, [])
41
42         return html`
43           ${messages.map((message) => html` <div>${message}</div> `)}
44
45           <form onSubmit=${onSendMessage}>
46             <input type="text" />
47             <button>Send</button>
48           </form>
49         `
50       }
51
52       render(html`<${Chat} />`, document.getElementById('app'))
53     </script>
54   </body>
55 </html>

Third-party and deps.ts convention

We can use third-party libraries in the same way we use the Deno Standard Library, by importing directly the URL of the module.

However, the ecosystem in https://deno.land/x/ is quite small yet.

But hey, I have good news for you, we can use packages from https://www.pika.dev .

Thanks to tools like Parcel or Minibundle we can compile Node libraries into modules to re-use them in Deno projects .

We are going to use the camel-case package to transform every chat message to camelCase!

Let’s add this import in our chat.ts file:

import { camelCase } from 'https://cdn.pika.dev/camel-case@^4.1.1';
// ...before code
const message = camelCase(typeof event === 'string' ? event : '')
// ... before code

That’s it. Running again the server.ts is going to download the camel-case package. Now you can see that it works:

However, if I want to use this camelCase helper in more than one file, it’s cumbersome to add the full import everywhere.

The URL indicates which version of the package we have to use.

This means that if we want to upgrade a dependency we will need to search and replace all the imports. This could cause us problems, but don’t worry, there is a Deno convention for the dependencies that solves this.

Creating a deps.ts file to export all project dependencies.

// deps.ts file
export { camelCase } from 'https://cdn.pika.dev/camel-case@^4.1.1';

and

// chat.ts file
import { camelCase } from './deps.ts';
// ...
const message = camelCase(typeof event === 'string' ? event : '')

Conclusion

We learned about how Deno works by creating a simple chat app in TypeScript.

We did it without npm, package.json, node_modules, webpack, babel, jest, prettier… because we don’t need them, Deno simplifies this.

We explored important things to begin with a Deno project: Permissions, deno commands, how to use deno internals, how to use third-party dependencies, serving a file, websockets, formating files, testing, debugging, etc.

I hope this article will be useful to start using Deno 1.0.0 in your projects when it comes out on May 13, 2020.