On this page
Deno Namespace APIs
The global Deno
namespace contains APIs that are not web standard, including
APIs for reading from files, opening TCP sockets, serving HTTP, and executing
subprocesses, etc.
Below we highlight some of the most important Deno APIs to know.
File System Jump to heading
The Deno runtime comes with various functions for working with files and directories. You will need to use --allow-read and --allow-write permissions to gain access to the file system.
Refer to the links below for code examples of how to use the file system functions.
- Reading files in several different ways
- Reading files in streams
- Reading a text file (
Deno.readTextFile
) - Writing a text file (
Deno.writeTextFile
)
Network Jump to heading
The Deno runtime comes with built-in functions for dealing with connections to network ports.
Refer to the links below for code examples for common functions.
- Connect to the hostname and port (
Deno.connect
) - Announcing on the local transport address (
Deno.listen
)
Subprocesses Jump to heading
The Deno runtime comes with built-in functions for spinning up subprocesses.
Refer to the links below for code samples of how to create a subprocess.
Errors Jump to heading
The Deno runtime comes with 20 error classes that can be raised in response to a number of conditions.
Some examples are:
Deno.errors.NotFound;
Deno.errors.WriteZero;
They can be used as below:
try {
const file = await Deno.open("./some/file.txt");
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
console.error("the file was not found");
} else {
// otherwise re-throw
throw error;
}
}
HTTP Server Jump to heading
Deno has two HTTP Server APIs:
Deno.serve
: native, higher-level, supports HTTP/1.1 and HTTP2, this is the preferred API to write HTTP servers in Deno.Deno.serveHttp
: native, low-level, supports HTTP/1.1 and HTTP2.
To start an HTTP server on a given port, use the Deno.serve
function. This
function takes a handler function that will be called for each incoming request,
and is expected to return a response (or a promise resolving to a response). For
example:
Deno.serve((_req) => {
return new Response("Hello, World!");
});
By default Deno.serve
will listen on port 8000
, but this can be changed by
passing in a port number in options bag as the first or second argument.
You can read more about how to use the HTTP server APIs.
Permissions Jump to heading
Permissions are granted from the CLI when running the deno
command. User code
will often assume its own set of required permissions, but there is no guarantee
during execution that the set of granted permissions will align with this.
In some cases, ensuring a fault-tolerant program requires a way to interact with the permission system at runtime.
Permission descriptors Jump to heading
On the CLI, read permission for /foo/bar
is represented as
--allow-read=/foo/bar
. In runtime JS, it is represented as the following:
const desc = { name: "read", path: "/foo/bar" } as const;
Other examples:
// Global write permission.
const desc1 = { name: "write" } as const;
// Write permission to `$PWD/foo/bar`.
const desc2 = { name: "write", path: "foo/bar" } as const;
// Global net permission.
const desc3 = { name: "net" } as const;
// Net permission to 127.0.0.1:8000.
const desc4 = { name: "net", host: "127.0.0.1:8000" } as const;
// High-resolution time permission.
const desc5 = { name: "hrtime" } as const;
See PermissionDescriptor
in API
reference for more details. Synchronous API counterparts (ex.
Deno.permissions.querySync
) exist for all the APIs described below.
Query permissions Jump to heading
Check, by descriptor, if a permission is granted or not.
// deno run --allow-read=/foo main.ts
const desc1 = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.query(desc1));
// PermissionStatus { state: "granted", partial: false }
const desc2 = { name: "read", path: "/foo/bar" } as const;
console.log(await Deno.permissions.query(desc2));
// PermissionStatus { state: "granted", partial: false }
const desc3 = { name: "read", path: "/bar" } as const;
console.log(await Deno.permissions.query(desc3));
// PermissionStatus { state: "prompt", partial: false }
If --deny-read
flag was used to restrict some of the filepaths, the result
will contain partial: true
describing that not all subpaths have permissions
granted:
// deno run --allow-read=/foo --deny-read=/foo/bar main.ts
const desc1 = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.query(desc1));
// PermissionStatus { state: "granted", partial: true }
const desc2 = { name: "read", path: "/foo/bar" } as const;
console.log(await Deno.permissions.query(desc2));
// PermissionStatus { state: "denied", partial: false }
const desc3 = { name: "read", path: "/bar" } as const;
console.log(await Deno.permissions.query(desc3));
// PermissionStatus { state: "prompt", partial: false }
Permission states Jump to heading
A permission state can be either "granted", "prompt" or "denied". Permissions
which have been granted from the CLI will query to { state: "granted" }
. Those
which have not been granted query to { state: "prompt" }
by default, while
{ state: "denied" }
reserved for those which have been explicitly refused.
This will come up in Request permissions.
Permission strength Jump to heading
The intuitive understanding behind the result of the second query in
Query permissions is that read access was granted to
/foo
and /foo/bar
is within /foo
so /foo/bar
is allowed to be read. This
hold true, unless the CLI-granted permission is partial to the queried
permissions (as an effect of using a --deny-*
flag).
We can also say that desc1
is
stronger than
desc2
. This means that for any set of CLI-granted permissions:
- If
desc1
queries to{ state: "granted", partial: false }
then so mustdesc2
. - If
desc2
queries to{ state: "denied", partial: false }
then so mustdesc1
.
More examples:
const desc1 = { name: "write" } as const;
// is stronger than
const desc2 = { name: "write", path: "/foo" } as const;
const desc3 = { name: "net", host: "127.0.0.1" } as const;
// is stronger than
const desc4 = { name: "net", host: "127.0.0.1:8000" } as const;
Request permissions Jump to heading
Request an ungranted permission from the user via CLI prompt.
// deno run main.ts
const desc1 = { name: "read", path: "/foo" } as const;
const status1 = await Deno.permissions.request(desc1);
// ⚠️ Deno requests read access to "/foo". Grant? [y/n (y = yes allow, n = no deny)] y
console.log(status1);
// PermissionStatus { state: "granted", partial: false }
const desc2 = { name: "read", path: "/bar" } as const;
const status2 = await Deno.permissions.request(desc2);
// ⚠️ Deno requests read access to "/bar". Grant? [y/n (y = yes allow, n = no deny)] n
console.log(status2);
// PermissionStatus { state: "denied", partial: false }
If the current permission state is "prompt", a prompt will appear on the user's
terminal asking them if they would like to grant the request. The request for
desc1
was granted so its new status is returned and execution will continue as
if --allow-read=/foo
was specified on the CLI. The request for desc2
was
denied so its permission state is downgraded from "prompt" to "denied".
If the current permission state is already either "granted" or "denied", the request will behave like a query and just return the current status. This prevents prompts both for already granted permissions and previously denied requests.
Revoke permissions Jump to heading
Downgrade a permission from "granted" to "prompt".
// deno run --allow-read=/foo main.ts
const desc = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.revoke(desc));
// PermissionStatus { state: "prompt", partial: false }
What happens when you try to revoke a permission which is partial to one granted on the CLI?
// deno run --allow-read=/foo main.ts
const desc = { name: "read", path: "/foo/bar" } as const;
console.log(await Deno.permissions.revoke(desc));
// PermissionStatus { state: "prompt", partial: false }
const cliDesc = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.revoke(cliDesc));
// PermissionStatus { state: "prompt", partial: false }
The CLI-granted permission, which implies the revoked permission, was also revoked.
To understand this behavior, imagine that Deno stores an internal set of
explicitly granted permission descriptors. Specifying --allow-read=/foo,/bar
on the CLI initializes this set to:
[
{ name: "read", path: "/foo" },
{ name: "read", path: "/bar" },
];
Granting a runtime request for { name: "write", path: "/foo" }
updates the set
to:
[
{ name: "read", path: "/foo" },
{ name: "read", path: "/bar" },
{ name: "write", path: "/foo" },
];
Deno's permission revocation algorithm works by removing every element from this set which is stronger than the argument permission descriptor.
Deno does not allow "fragmented" permission states, where some strong permission
is granted with exclusions of weak permissions implied by it. Such a system
would prove increasingly complex and unpredictable as you factor in a wider
variety of use cases and the "denied"
state. This is a calculated trade-off of
granularity for security.
import.meta Jump to heading
Deno supports a number of properties and methods on the
import.meta
API. It can be used to get information about the module, such as the module's
URL.
import.meta.url Jump to heading
Returns the URL of the current module.
console.log(import.meta.url);
$ deno run main.ts
file:///dev/main.ts
$ deno run https:/example.com/main.ts
https://example.com/main.ts
import.meta.main Jump to heading
Returns whether the current module is the entry point to your program.
import "./other.ts";
console.log(`Is ${import.meta.url} the main module?`, import.meta.main);
console.log(`Is ${import.meta.url} the main module?`, import.meta.main);
$ deno run main.ts
Is file:///dev/other.ts the main module? false
Is file:///dev/main.ts the main module? true
import.meta.filename Jump to heading
This property is only available for local modules (module that have
file:///...
specifier) and returns undefined
for remote modules.
Returns the fully resolved path to the current module. The value contains OS specific path separators.
console.log(import.meta.filename);
On Unix:
$ deno run main.ts
/dev/main.ts
$ deno run https://example.com/main.ts
undefined
On Windows:
$ deno run main.ts
C:\dev\main.ts
$ deno run https://example.com/main.ts
undefined
import.meta.dirname Jump to heading
This property is only available for local modules (module that have
file:///...
specifier) and returns undefined
for remote modules.
Returns the fully resolved path to the directory containing the current module. The value contains OS specific path separators.
console.log(import.meta.dirname);
On Unix:
$ deno run main.ts
/dev/
$ deno run https://example.com/main.ts
undefined
On Windows:
$ deno run main.ts
C:\dev\
$ deno run https://example.com/main.ts
undefined
import.meta.resolve Jump to heading
Resolve specifiers relative to the current module.
const worker = new Worker(import.meta.resolve("./worker.ts"));
The import.meta.resolve
API takes into account the currently applied import
map, which gives you the ability to resolve "bare" specifiers as well.
With such import map loaded...
{
"imports": {
"fresh": "https://deno.land/x/[email protected]/dev.ts"
}
}
...you can now resolve:
console.log(import.meta.resolve("fresh"));
$ deno run resolve.js
https://deno.land/x/[email protected]/dev.ts
FFI Jump to heading
The FFI (foreign function interface) API allows users to call libraries written
in native languages that support the C ABIs (C/C++, Rust, Zig, V, etc.) using
Deno.dlopen
.
Here's an example showing how to call a Rust function from Deno:
// add.rs
#[no_mangle]
pub extern "C" fn add(a: isize, b: isize) -> isize {
a + b
}
Compile it to a C dynamic library (libadd.so
on Linux):
rustc --crate-type cdylib add.rs
In C you can write it as:
// add.c
int add(int a, int b) {
return a + b;
}
And compile it:
// unix
cc -c -o add.o add.c
cc -shared -W -o libadd.so add.o
// Windows
cl /LD add.c /link /EXPORT:add
Calling the library from Deno:
// ffi.ts
// Determine library extension based on
// your OS.
let libSuffix = "";
switch (Deno.build.os) {
case "windows":
libSuffix = "dll";
break;
case "darwin":
libSuffix = "dylib";
break;
default:
libSuffix = "so";
break;
}
const libName = `./libadd.${libSuffix}`;
// Open library and define exported symbols
const dylib = Deno.dlopen(
libName,
{
"add": { parameters: ["isize", "isize"], result: "isize" },
} as const,
);
// Call the symbol `add`
const result = dylib.symbols.add(35, 34); // 69
console.log(`Result from external addition of 35 and 34: ${result}`);
Run with --allow-ffi
and --unstable
flag:
deno run --allow-ffi --unstable ffi.ts
Non-blocking FFI Jump to heading
There are many use cases where users might want to run CPU-bound FFI functions in the background without blocking other tasks on the main thread.
As of Deno 1.15, symbols can be marked nonblocking
in Deno.dlopen
. These
function calls will run on a dedicated blocking thread and will return a
Promise
resolving to the desired result
.
Example of executing expensive FFI calls with Deno:
// sleep.c
#ifdef _WIN32
#include <Windows.h>
#else
#include <time.h>
#endif
int sleep(unsigned int ms) {
#ifdef _WIN32
Sleep(ms);
#else
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
#endif
}
Calling it from Deno:
// nonblocking_ffi.ts
const library = Deno.dlopen(
"./sleep.so",
{
sleep: {
parameters: ["usize"],
result: "void",
nonblocking: true,
},
} as const,
);
library.symbols.sleep(500).then(() => console.log("After"));
console.log("Before");
Result:
$ deno run --allow-ffi --unstable unblocking_ffi.ts
Before
After
Callbacks Jump to heading
Deno FFI API supports creating C callbacks from JavaScript functions for calling back into Deno from dynamic libraries. An example of how callbacks are created and used is as follows:
// callback_ffi.ts
const library = Deno.dlopen(
"./callback.so",
{
set_status_callback: {
parameters: ["function"],
result: "void",
},
start_long_operation: {
parameters: [],
result: "void",
},
check_status: {
parameters: [],
result: "void",
},
} as const,
);
const callback = new Deno.UnsafeCallback(
{
parameters: ["u8"],
result: "void",
} as const,
(success: number) => {},
);
// Pass the callback pointer to dynamic library
library.symbols.set_status_callback(callback.pointer);
// Start some long operation that does not block the thread
library.symbols.start_long_operation();
// Later, trigger the library to check if the operation is done.
// If it is, this call will trigger the callback.
library.symbols.check_status();
If an UnsafeCallback
's callback function throws an error, the error will get
propagated up to the function that triggered the callback to be called (above,
that would be check_status()
) and can be caught there. If a callback returning
a value throws then Deno will return 0 (null pointer for pointers) as the
result.
UnsafeCallback
is not deallocated by default as it can cause use-after-free
bugs. To properly dispose of an UnsafeCallback
its close()
method must be
called.
const callback = new Deno.UnsafeCallback(
{ parameters: [], result: "void" } as const,
() => {},
);
// After callback is no longer needed
callback.close();
// It is no longer safe to pass the callback as a parameter.
It is also possible for native libraries to setup interrupt handlers and to have
those directly trigger the callback. However, this is not recommended and may
cause unexpected side-effects and undefined behaviour. Preferably any interrupt
handlers would only set a flag that can later be polled similarly to how
check_status()
is used above.
Supported types Jump to heading
Here's a list of types supported currently by the Deno FFI API.
FFI Type | Deno | C | Rust |
---|---|---|---|
i8 |
number |
char / signed char |
i8 |
u8 |
number |
unsigned char |
u8 |
i16 |
number |
short int |
i16 |
u16 |
number |
unsigned short int |
u16 |
i32 |
number |
int / signed int |
i32 |
u32 |
number |
unsigned int |
u32 |
i64 |
number | bigint |
long long int |
i64 |
u64 |
number | bigint |
unsigned long long int |
u64 |
usize |
number | bigint |
size_t |
usize |
isize |
number | bigint |
size_t |
isize |
f32 |
number | bigint |
float |
f32 |
f64 |
number | bigint |
double |
f64 |
void [1] |
undefined |
void |
() |
pointer |
{} | null |
void * |
*mut c_void |
buffer [2] |
TypedArray | null |
uint8_t * |
*mut u8 |
function [3] |
{} | null |
void (*fun)() |
Option<extern "C" fn()> |
{ struct: [...] } [4] |
TypedArray |
struct MyStruct |
MyStruct |
As of Deno 1.25, the pointer
type has been split into a pointer
and a
buffer
type to ensure users take advantage of optimizations for Typed Arrays,
and as of Deno 1.31 the JavaScript representation of pointer
has become an
opaque pointer object or null
for null pointers.
- [1]
void
type can only be used as a result type. - [2]
buffer
type accepts TypedArrays as parameter, but it always returns a pointer object ornull
when used as result type like thepointer
type. - [3]
function
type works exactly the same as thepointer
type as a parameter and result type. - [4]
struct
type is for passing and returning C structs by value (copy). Thestruct
array must enumerate each of the struct's fields' type in order. The structs are padded automatically: Packed structs can be defined by using an appropriate amount ofu8
fields to avoid padding. Only TypedArrays are supported as structs, and structs are always returned asUint8Array
s.
deno_bindgen Jump to heading
deno_bindgen
is the official tool
to simplify glue code generation of Deno FFI libraries written in Rust.
It is similar to wasm-bindgen
in
the Rust Wasm ecosystem.
Here's an example showing its usage:
// mul.rs
use deno_bindgen::deno_bindgen;
#[deno_bindgen]
struct Input {
a: i32,
b: i32,
}
#[deno_bindgen]
fn mul(input: Input) -> i32 {
input.a * input.b
}
Run deno_bindgen
to generate bindings. You can now directly import them into
Deno:
// mul.ts
import { mul } from "./bindings/bindings.ts";
mul({ a: 10, b: 2 }); // 20
Any issues related to deno_bindgen
should be reported at
https://github.com/denoland/deno_bindgen/issues
Program Lifecycle Jump to heading
Deno supports browser compatible lifecycle events:
load
: fired when the whole page has loaded, including all dependent resources such as stylesheets and images.beforeunload
: fired when the event loop has no more work to do and is about to exit. Scheduling more asynchronous work (like timers or network requests) will cause the program to continue.unload
: fired when the document or a child resource is being unloaded.unhandledrejection
: fired when a promise that has no rejection handler is rejected, ie. a promise that has no.catch()
handler or a second argument to.then()
.rejectionhandled
: fired when a.catch()
handler is added to a a promise that has already rejected. This event is fired only if there'sunhandledrejection
listener installed that prevents propagation of the event (which would result in the program terminating with an error).
You can use these events to provide setup and cleanup code in your program.
Listeners for load
events can be asynchronous and will be awaited, this event
cannot be canceled. Listeners for beforeunload
need to be synchronous and can
be cancelled to keep the program running. Listeners for unload
events need to
be synchronous and cannot be cancelled.
main.ts
import "./imported.ts";
const handler = (e: Event): void => {
console.log(`got ${e.type} event in event handler (main)`);
};
globalThis.addEventListener("load", handler);
globalThis.addEventListener("beforeunload", handler);
globalThis.addEventListener("unload", handler);
globalThis.onload = (e: Event): void => {
console.log(`got ${e.type} event in onload function (main)`);
};
globalThis.onbeforeunload = (e: Event): void => {
console.log(`got ${e.type} event in onbeforeunload function (main)`);
};
globalThis.onunload = (e: Event): void => {
console.log(`got ${e.type} event in onunload function (main)`);
};
console.log("log from main script");
const handler = (e: Event): void => {
console.log(`got ${e.type} event in event handler (imported)`);
};
globalThis.addEventListener("load", handler);
globalThis.addEventListener("beforeunload", handler);
globalThis.addEventListener("unload", handler);
globalThis.onload = (e: Event): void => {
console.log(`got ${e.type} event in onload function (imported)`);
};
globalThis.onbeforeunload = (e: Event): void => {
console.log(`got ${e.type} event in onbeforeunload function (imported)`);
};
globalThis.onunload = (e: Event): void => {
console.log(`got ${e.type} event in onunload function (imported)`);
};
console.log("log from imported script");
A couple notes on this example:
addEventListener
andonload
/onunload
are prefixed withglobalThis
, but you could also useself
or no prefix at all. It is not recommended to usewindow
as a prefix.- You can use
addEventListener
and/oronload
/onunload
to define handlers for events. There is a major difference between them, let's run the example:
$ deno run main.ts
log from imported script
log from main script
got load event in event handler (imported)
got load event in event handler (main)
got load event in onload function (main)
got onbeforeunload event in event handler (imported)
got onbeforeunload event in event handler (main)
got onbeforeunload event in onbeforeunload function (main)
got unload event in event handler (imported)
got unload event in event handler (main)
got unload event in onunload function (main)
All listeners added using addEventListener
were run, but onload
,
onbeforeunload
and onunload
defined in main.ts
overrode handlers defined
in imported.ts
.
In other words, you can use addEventListener
to register multiple "load"
or
"unload"
event handlers, but only the last defined onload
, onbeforeunload
,
onunload
event handlers will be executed. It is preferable to use
addEventListener
when possible for this reason.
beforeunload Jump to heading
// beforeunload.js
let count = 0;
console.log(count);
globalThis.addEventListener("beforeunload", (e) => {
console.log("About to exit...");
if (count < 4) {
e.preventDefault();
console.log("Scheduling more work...");
setTimeout(() => {
console.log(count);
}, 100);
}
count++;
});
globalThis.addEventListener("unload", (e) => {
console.log("Exiting");
});
count++;
console.log(count);
setTimeout(() => {
count++;
console.log(count);
}, 100);
Running this program will print:
$ deno run beforeunload.js
0
1
2
About to exit...
Scheduling more work...
3
About to exit...
Scheduling more work...
4
About to exit...
Exiting
unhandledrejection event Jump to heading
This event is fired when a promise that has no rejection handler is rejected, ie. a promise that has no .catch() handler or a second argument to .then().
// unhandledrejection.js
globalThis.addEventListener("unhandledrejection", (e) => {
console.log("unhandled rejection at:", e.promise, "reason:", e.reason);
e.preventDefault();
});
function Foo() {
this.bar = Promise.reject(new Error("bar not available"));
}
new Foo();
Promise.reject();
Running this program will print:
$ deno run unhandledrejection.js
unhandled rejection at: Promise {
<rejected> Error: bar not available
at new Foo (file:///dev/unhandled_rejection.js:7:29)
at file:///dev/unhandled_rejection.js:10:1
} reason: Error: bar not available
at new Foo (file:///dev/unhandled_rejection.js:7:29)
at file:///dev/unhandled_rejection.js:10:1
unhandled rejection at: Promise { <rejected> undefined } reason: undefined