A list of every web API in Deno
Have you ever wondered how web compatible Deno is? Probably not, but I did today. To answer that question, I am writing this blog post: I’ll list and explain every single web API implemented in Deno. Get yourself something to drink, because this is going to take a while to go through.
Before we get into it though, I just want to set some ground rules:
- I am not including any JS language features. Only web platform features.
- I will include the few features that are still marked as
--unstable
, but will mention that they are not yet stable. - I am not including any features that are not a concrete API. For example JSON modules are implemented in Deno, but are more of an abstract concept rather than a concrete API and thus are not included in this list.
Table of Contents
atob
andbtoa
setTimeout
andclearTimeout
setInterval
andclearInterval
crypto
fetch
,Request
,Response
, andHeaders
Blob
andFile
TextEncoder
andTextDecoder
FormData
performance
structuredClone
URL
andURLSearchParams
console
Worker
Event
andEventTarget
WebSocket
ReadableStream
andWritableStream
TransformStream
WebSocketStream
TextEncoderStream
andTextDecoderStream
CompressionStream
andDecompressionStream
URLPattern
alert
,confirm
, andprompt
localStorage
andsessionStorage
navigator
- WebGPU
MessageChannel
BroadcastChannel
atob
and btoa
These two functions are used to encode and decode base64 strings. They’re old - Firefox 1, released on the 9th of November 2004, already had support.
atob("SGVsbG8gV29ybGQ="); // "Hello World"
btoa("Hello World"); // "SGVsbG8gV29ybGQ="
setTimeout
and clearTimeout
setTimeout
is another super old web feature. It’s used to schedule a function
to be called after a certain amount of time has passed. It returns a numeric ID
that can be used to cancel the timeout with clearTimeout
.
Beware that Deno implements this API just like in the browser: returning a number. Node.js also implements this API, but with a non-standard behavior where it returns an object instead of a number.
setTimeout(() => {
console.log("This prints after 1 second.");
}, 1000);
const timerId = setTimeout(() => {
console.log("This doesn't print at all.");
}, 500);
clearTimeout(timerId);
console.log("This prints immediately.");
setInterval
and clearInterval
setInterval
and clearInterval
are very similar to setTimeout
and
clearTimeout
. The difference is that setInterval
calls the callback function
every X milliseconds, rather than only once after X milliseconds.
Same disclaimer as with setTimeout
applies here: Deno implements this API just
like the browser, while Node has a non standard object as the return value.
crypto
Deno implements the Web Cryptography API completely. This API can be used to do various low and high level cryptographic operations. For example you can:
- generate a random UUID with
crypto.randomUUID()
- generate a
Uint8Array
filled with random bytes withcrypto.getRandomValues()
- sign and verify data with
crypto.subtle.sign()
andcrypto.subtle.verify()
with RSA, ECDSA, and many other algorithms. - generate hashes with
crypto.subtle.digest()
. - encrypt and decrypt data with
crypto.subtle.encrypt()
andcrypto.subtle.decrypt()
. - generate RSA, ECDSA, HMAC, or AES keys with
crypto.subtle.generateKey()
Deno’s implementation of the Web Cryptography API was completed just a few weeks ago, but the wait was totally worth it. Deno passes more web platform tests than both Chrome and Firefox. Only Safari has us slightly beat:
Engine | % of tests passing |
---|---|
Safari | 99.8% |
Deno | 98.1% |
Chrome/Edge | 94.5% |
Firefox | 93.4% |
You can see the current data for yourself on wpt.fyi.
const uuid = crypto.randomUUID();
const bytes = await crypto.getRandomValues(new Uint8Array(16));
const digest = await crypto.subtle.digest("SHA-256", bytes);
fetch
, Request
, Response
, and Headers
Deno implements the maybe most popular modern web API: fetch
. It’s
used to make HTTP requests. Usage is super simple: the first argument is the URL
you want to request, and the second argument is an optional object with options,
like method
, headers
, or body
.
The fetch
API in Deno is implemented natively in the runtime, backed by the
blazing fast hyper
HTTP implementation in Rust. It supports HTTP/1.1
and HTTP/2 servers, full duplex streaming, native gzip/deflate/brotli decoding,
and is highly compatible with the web platform.
Just like all of our other web APIs, our implementation of fetch
is tested
using the same web platform tests test-suite as all the browsers. This
makes sure that Deno’s implementation is compatible with the web platform. To
our knowledge, we are the only server side runtime that tests fetch
against
the canonical web platform tests right now.
Deno also implements all of the objects surrounding fetch
:
Request
, Response
, and Headers
. These
objects represent a HTTP request, a HTTP response, and a list of HTTP headers
respectively. Because Deno is a web-first runtime, our HTTP server uses these
same objects to represent the request and response.
That makes proxying requests super easy.
const resp = await fetch("https://whats-my-ip.deno.dev/");
const text = await resp.text();
console.log(text);
Blob
and File
Blob
and File
both represent binary data. The big difference between them
and Uint8Array
is that they can store their data in memory or on disk, thus
making them ideal for large binary blobs. Because of the possibility of storing
data on disk, the data backed by Blob
and File
is only available
asynchronously.
Data can be retrieved using the .arrayBuffer()
, .text()
, and .stream()
methods, or the FileReader
API.
const blob = new Blob(["Hello World"]);
const text = await blob.text();
console.log(text);
const file = new File(["Hello World"], "hello.txt");
console.log(file.name);
console.log(file.size);
const bytes = await file.arrayBuffer();
console.log(new Uint8Array(bytes));
TextEncoder
and TextDecoder
Sometimes you need to encode or decode strings into or from a binary
representation. Deno provides the TextEncoder
and
TextDecoder
APIs for this. They can be used to encode strings
into a Uint8Array
, and decode Uint8Array
s into strings. All of the text
encodings that are available in the browser are also available in Deno.
const encoder = new TextEncoder();
const bytes = encoder.encode("Hello World");
console.log(bytes);
const decoder = new TextDecoder();
const text = decoder.decode(bytes);
console.log(text);
FormData
When interacting with HTTP APIs, sometimes you need to send data as
multipart/form-data
. This can be done using the FormData
API. It
is a JavaScript structure that lets you easily append key-value pairs or files
to a form to be sent as multipart data.
Because Deno also uses the same Request
and Response
objects from fetch
for the native HTTP server, FormData
can be used to easily decode multipart
form data from incoming requests by calling await request.formData()
. Neat
huh?
const formData = new FormData();
formData.append("name", "Deno");
formData.append("age", "3");
formData.append("file", new File(["Hello World"], "hello.txt"));
const resp = await fetch("https://httpbin.org/post", {
method: "POST",
body: formData,
});
performance
The performance
allows for accurate time measurement, for example using
performance.now()
. It can also be used to directly measure the time spent on
some operation using performance.mark()
and performance.measure()
.
const start = performance.now();
await fetch("https://httpbin.org/delay/1");
const end = performance.now();
console.log("Took", end - start, "milliseconds");
structuredClone
The structuredClone
API is used to deep clone objects. It is a very new API,
but is already shipping in all major browsers.
Compared to shallow cloning, deep cloning doesn’t just copy the shape of the outermost object, but also the shape of all nested objects and arrays. It can also clone objects with circular references.
const obj = {
foo: "bar",
baz: {
qux: "quux",
},
};
const clone = structuredClone(obj);
console.log(obj === clone); // false
console.log(obj.baz === clone.baz); // false
obj.baz.qux = "quuz";
console.log(obj.baz.qux); // quuz
console.log(clone.baz.qux); // quux
URL
and URLSearchParams
URLs are integral to anything having to do with the web. Deno implements the
URL
and URLSearchParams
APIs as they are specified
by the WHATWG. This means that our URL and query string parsing is
exactly the same as the browser. This can prevent security issues around one
system parsing a URL slightly differently to another system.
const url = new URL("https://example.com/foo");
console.log(url.href); // https://example.com/foo
console.log(url.hostname); // example.com
console.log(url.searchParams.get("name")); // undefined
url.searchParams.append("name", "bar");
console.log(url.href); // https://example.com/foo?name=bar
console
console
may be the most useful web API out there. Printf debugging anyone? You
probably know about console.log()
already, so here are some other cool
features of console
that Deno implements that you may not know about:
%c
marker can be used to add color to logging output using CSS. Deno
understands this natively - no need to remember ANSI escape codes anymore to do
colored logging:
console.log("%cHello, world!", "color: red");
console.log("%cHello, %cworld!", "font-weight: bold", "font-style: italic");
Another cool console
feature we implement is the console.time
API. It makes
it super simple to figure out how long some operation took:
console.time("foo");
await new Promise((resolve) => setTimeout(resolve, 1000));
console.timeEnd("foo");
Worker
JavaScript execution is always single threaded - the language has no built in
concurrency primitives. To support workloads that require multiple threads, the
web has the concept of Worker
. Workers are additional JavaScript
execution contexts that run on a separate thread, completely isolated from the
main thread.
Deno implements workers natively, just like in the browser. Because the APIs are
identical you can use libraries that are written for the browser in Deno without
any changes. An example of such a library is comlink
- it makes
using workers super simple by abstracting away the details of cross worker
communication.
// main.js
import * as Comlink from "https://cdn.skypack.dev/comlink@4.3.1?dts";
const url = new URL("./worker.js", import.meta.url);
const worker = new Worker(url, { type: "module" });
const obj = Comlink.wrap(worker);
console.log(`Counter: ${await obj.counter}`);
await obj.inc();
console.log(`Counter: ${await obj.counter}`);
worker.terminate();
// worker.js
import * as Comlink from "https://cdn.skypack.dev/comlink@4.3.1?dts";
const obj = {
counter: 0,
inc() {
this.counter++;
},
};
Comlink.expose(obj);
Event
and EventTarget
In the browser events and event targets are the basis of all interactive
experiences. They allow you to register a callback to be called when some event
occurs. FileReader
is an EventTarget
for example: it emit events when a
chunks of the file are read, or when the file is completely read, or when an
error occurs.
Modern APIs often don’t use events and callbacks anymore, instead using
Promise
s. These have much better usability and readability because of
async/await. Nonetheless Event
and EventTarget
are still used by many APIs,
so Deno implements them just like browsers.
const target = new EventTarget();
target.addEventListener("foo", (event) => {
console.log(event);
});
target.dispatchEvent(new Event("foo"));
WebSocket
Just like how Deno implements fetch
do to HTTP requests just like in the
browser, Deno also implements the WebSocket
API. WebSockets are
great way to do bi-directional communication between a client and server.
Because the WebSocket protocol works the same on both ends of the connection (client and server), Deno also uses this same API for server-side (incoming) WebSocket connections on the native HTTP server.
const ws = new WebSocket("ws://localhost:4500");
ws.onopen = () => {
ws.send("Hello, world!");
};
ws.onmessage = (event) => {
console.log(event.data);
ws.close();
};
ReadableStream
and WritableStream
Streaming is a critical part of modern web applications. It is a necessity when working with data that is larger than can fit into available memory at once. It is also a great way to reduce the TTFB when working with large files.
Deno supports the same streams API as browsers. This is very powerful, because
it allows Deno users to make use of stream transformers written for the browser.
It also lets web developers use stream transforms written for Deno on their
website. For example, the std/streams
module from Deno’s
standard library can be used from both Deno and the browser to do things like
split streams of text at newlines.
Readable streams are the most common type of stream, and are used when you want to read data from a source. For example, you might want to stream the response body of a HTTP request, or the contents of a file on disk.
Writable streams are the inverse - they are used when you want to write data to
a destination in chunks. One such case is the sending side of a
WebSocketStream
(more on this below).
const body = new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([1, 2, 3]));
controller.enqueue(new Uint8Array([4, 5, 6]));
controller.close();
},
});
const resp = await fetch("https://httpbin.org/anything", { body });
for await (const chunk of resp.body) {
console.log(chunk);
}
TransformStream
Next to the low level stream primitives ReadableStream
and WritableStream
,
the web also has a general purpose API to transform streaming data called
TransformStream
. They have a readable and a writable end, and a “transformer”
function. For each chunk that is written to the writable end, the transformer
function is called. It can then enqueue transformed chunks to the readable end
for the consumer to read.
const input = new ReadableStream({
start(controller) {
controller.enqueue("Hello, ");
controller.enqueue("world!");
controller.close();
},
});
const transformer = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toUpperCase());
},
});
const output = input.pipeThrough(transformer);
for await (const chunk of output) {
console.log(chunk);
}
WebSocketStream
As mentioned earlier, Deno implements the WebSocket
API that is built on top
of EventTarget
. This can sometimes be pretty painful to use. As an alternative
you can use the WebSocketStream
API which is based on streams and promises. It
is a lot easier to use when you are using async/await.
const wss = new WebSocketStream("wss://example.com");
const { writable, readable } = await wss.connection;
const writer = writable.getWriter();
await writer.write("Hello server!");
for await (const message of readable) {
console.log("new message:", message);
}
NOTE: this API is still experimental and may change in the future. You need to use the –unstable flag to use it.
TextEncoderStream
and TextDecoderStream
The TextEncoderStream and TextDecoderStream are transform streams that can encode and decode strings to and from bytes in chunks. Unlike TextEncoder and TextDecoder are usually used for fully synchronous operations, TextEncoderStream and TextDecoderStream are fully streaming.
This API makes it super easy to convert the response stream from a fetch call to string chunks:
const response = await fetch("https://example.com");
const body = response.body.pipeThrough(new TextDecoderStream());
for await (const chunk of body) {
console.log(chunk);
}
CompressionStream
and DecompressionStream
Another operation that often needs to performed on streaming data is compression
and decompression. You can use CompressionStream
to gzip compress some data
before uploading to a storage bucket for example. When downloading that data
again you can decompress it with DecompressionStream
.
Just like Chrome, Deno supports gzip
and deflate
compression. Support for
brotli
compression is coming soon.
const response = await fetch("https://example.com");
const body = response.body.pipeThrough(new CompressionStream("gzip"));
const file = await Deno.create("./file.gz");
for await (const chunk of body) {
await file.write(chunk);
}
CompressionStream
is available in the latest Deno canary build, but is not yet available in the 1.18 stable build. The first stable build it will be available in is Deno 1.19.
URLPattern
URLPattern is a new web API to match URLs against a
path-to-regexp
style pattern. It is super useful for
creating routing systems for HTTP servers for example. The API is available in
Chrome and Deno, and Firefox is interested in implementing it.
const pattern = new URLPattern({ pathname: "/hello/:name" });
const match = pattern.exec("https://example.com/hello/Deno");
console.log(match.pathname.groups);
alert
, confirm
and prompt
CLI applications often need to interact with the user to some extent. Deno
implements the simple dialog APIs alert
, confirm
and prompt
from the web
platform for this purpose. You can alert
a user with a message and wait for
acknowledgement, ask the user to confirm
a yes/no question, or prompt
the
user for a string response.
let name;
do {
name = prompt("What is your name?");
} while (!confirm(`Is your name ${name}?`));
alert(`Hello, ${name}!`);
localStorage
and sessionStorage
When writing CLI applications it is also often useful to persist a little bit of
state across runs (for example an API access token). This can be done trivially
with the localStorage
API. It is a persistent key-value store for each Deno
application.
const count = parseInt(localStorage.getItem("count") || "0");
console.log(`You have started the application ${count} times previously.`);
localStorage.setItem("count", count + 1);
navigator
The window.navigator
object contains some useful information about the current
system, like the number of available CPU cores / threads for example.
navigator.hardwareConcurrency
contains the number of logical CPU cores.
console.log("This system has", navigator.hardwareConcurrency, "CPU cores.");
WebGPU
All of the previous APIs have either been related to the CPU, or some I/O primitive. Deno also supports accessing the GPU through the WebGPU API. This low-level API can be thought of as “vulkan for the web”. It allows for efficient low level access to the GPU to perform rendering and compute tasks.
Deno bases it’s WebGPU implementation of the same Rust library that Firefox
uses: wgpu
.
const adapter = await navigator.gpu.requestAdapter();
console.log("GPU adapter:", adapter.name);
// this blog post is already long enough, I won't go into
// low level GPU programming here :-)
NOTE: this API is still experimental and may change in the future. You need to use the –unstable flag to use it.
MessageChannel
One lesser known web API is MessageChannel
. It is a pair of streams that can
be used to communicate between two workers. Each channel has two “ports”, each
of which can be either kept in the current worker or transferred to another
worker.
This allows for really complex communication channels between workers, without requiring a single centralized worker to act as a “message proxy”.
const channel = new MessageChannel();
const { port1, port2 } = channel;
port1.onmessage = (event) => {
console.log(event.data);
port1.close();
};
port2.postMessage("Hello, world!");
port2.close();
BroadcastChannel
BroadcastChannel is similar to MessageChannel in that it is a channel to communicate between workers. However, unlike MessageChannel, it is a 1 to many channel rather than a 1 to 1 channel.
const channel = new BroadcastChannel("my-channel");
channel.onmessage = (event) => {
console.log(event.data);
};
channel.postMessage("Hello, world!");
NOTE: this API is still experimental and may change in the future. You need to use the –unstable flag to use it.