DEV Community

Tianya School
Tianya School

Posted on

Deno Introduction-A Modern Alternative to Node.js

Deno, as a modern alternative to Node.js, offers numerous improvements and innovations, particularly in security, module systems, and developer experience. Although still in development, Deno is a compelling choice for developers seeking a simple, secure, and modern JavaScript/TypeScript development environment. As its community continues to grow, Deno’s potential and influence are expected to expand further.

Deno Fundamentals

  • Built-in Security Model: Deno enforces strict permission controls, requiring explicit permissions for actions like file read/write or network access.
  • TypeScript Support: Deno natively supports TypeScript, providing enhanced type checking and a better development experience.
  • ES Modules: Deno uses URLs or import maps for module imports, differing from Node.js’s CommonJS module system.

Command-Line Tools

  • deno run: Executes a single file.
  • deno test: Runs tests.
  • deno fmt: Formats code.
  • deno lint: Checks code style.

Configuration File

Deno’s configuration is primarily managed through the deno.json file, which allows customization of runtime behavior. Environment variables mainly affect Deno’s global configuration, such as logging levels or cache directories.

Deno Configuration File (deno.json)

The configuration file typically includes the following sections:

  1. permissions: Defines runtime permissions.
  2. importMap: Configures module import mappings.
  3. compilerOptions: TypeScript compiler options.
  4. lintRules: Linting rules (if using deno-lint).
  5. watch: Monitors file changes and automatically re-runs.
  6. reload: Automatically reloads modules.
{
  "permissions": {
    "env": true,
    "net": ["*"],
    "read": ["./data"],
    "write": ["./output"]
  },
  "importMap": {
    "imports": {
      "lodash": "https://cdn.skypack.dev/lodash@4.17.21",
      "my-local-module": "./src/my-local-module.ts"
    }
  },
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "lib": ["dom", "deno.ns"],
    "strict": true
  },
  "lintRules": {
    // ...
  },
  "watch": true,
  "reload": {
    "enabled": true,
    "include": ["./src"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Environment Variables

  • DENO_DIR: Specifies the configuration, cache, and download directory, defaulting to ~/.deno.
  • DENO_AUTH_TOKEN: Authentication token for accessing private modules.
  • DENO_LOGGING_LEVEL: Controls logging level, e.g., debug, info, warn, error.
  • DENO_CACHE: Custom cache directory.
  • DENO.land_proxy: Proxy settings for accessing deno.land.
  • DENO_NO_COLOR: Disables colored output if set.
# Linux/MacOS
export DENO_DIR=/path/to/custom/deno/dir
export DENO_LOGGING_LEVEL=debug

# Windows
set DENO_DIR=%USERPROFILE%\custom\deno\dir
set DENO_LOGGING_LEVEL=debug
Enter fullscreen mode Exit fullscreen mode

Note that not all configuration options can be set via environment variables; most configurations are defined in the deno.json file. Deno’s permissions are typically specified at runtime via command-line flags, not through configuration files or environment variables. For example: deno run --allow-read=./data your_script.ts.

Creating an HTTP Server

Creating an HTTP server in Deno is straightforward using the built-in std/http library.

// server.ts
import { serve } from "https://deno.land/std/http/server.ts";

const s = serve({ port: 8000 });
console.log("Server is running on http://localhost:8000");

for await (const req of s) {
  req.respond({ status: 200, body: "Hello, World!\n" });
}
Enter fullscreen mode Exit fullscreen mode

Code Breakdown:

  1. Import the serve Function: import { serve } from "https://deno.land/std/http/server.ts"; imports the serve function from Deno’s standard library to create an HTTP server.
  2. Start the Server: const s = serve({ port: 8000 }); creates and starts a server listening on port 8000. s is an iterable object representing each incoming request.
  3. Log Server Info: console.log("Server is running on http://localhost:8000"); informs users that the server is running and provides the access URL.
  4. Handle Requests: for await (const req of s) is an async iterator that waits for and processes each incoming HTTP request. req is a ServerRequest instance containing request details.
  5. Respond to Requests: req.respond({ status: 200, body: "Hello, World!\n" }); sends a response to the client with a 200 status code and a body of "Hello, World!\n".
  6. Run the Server: In the terminal, run the script with deno run --allow-net server.ts. The --allow-net flag is required to allow network access.

Fetching Remote Data

Fetching remote data in Deno typically uses the fetch API:

// fetch_data.ts
import { assert } from "https://deno.land/std/testing/asserts.ts";
import { json as parseJson } from "https://deno.land/std/io/ioutil.ts";

async function fetchData(url: string) {
  const response = await fetch(url);
  assert(response.ok, `Failed to fetch data: ${response.statusText}`);

  const data = await parseJson(await response.text());
  console.log(data);
}

// Example URL, replace with your desired data source
const remoteDataURL = "https://jsonplaceholder.typicode.com/todos/1";
fetchData(remoteDataURL);
Enter fullscreen mode Exit fullscreen mode

Code Breakdown:

Importing Modules:

  • assert ensures the HTTP request succeeds.
  • parseJson converts received text to a JSON object.

Defining the fetchData Function:

  • fetch(url) asynchronously sends an HTTP GET request to the specified URL.
  • response.ok checks if the HTTP status code is in the 200-299 range, indicating success.
  • response.text() retrieves the response body as text.
  • parseJson(text) parses the text into a JSON object.
  • console.log(data) logs the parsed data.

Calling fetchData:

  • fetchData(remoteDataURL) calls the function with an example URL to fetch remote data.

Running the Script:

  • Run with deno run --allow-net fetch_data.ts. The --allow-net flag is required for network access.

File System Operations

// file_operations.ts
import { readTextFile, writeTextFile } from "https://deno.land/std/fs/mod.ts";

// Read a file
const content = await readTextFile("example.txt");
console.log(content);

// Write to a file
const newContent = "This is new content.";
await writeTextFile("example.txt", newContent);
Enter fullscreen mode Exit fullscreen mode

Network Programming

// http_server.ts
import { serve } from "https://deno.land/std/http/server.ts";

const s = serve({ port: 8000 });

console.log("Server is running on http://localhost:8000");

for await (const req of s) {
  req.respond({ status: 200, body: "Hello, World!\n" });
}
Enter fullscreen mode Exit fullscreen mode

Asynchronous Programming

Deno uses async/await syntax for asynchronous operations, making code cleaner and easier to understand.

// async_example.ts
import { delay } from "https://deno.land/std/async/mod.ts";

async function asyncTask() {
  console.log("Task started...");
  await delay(1000); // Delay for 1 second
  console.log("Task completed.");
}

asyncTask();
Enter fullscreen mode Exit fullscreen mode

Asynchronous File Operations

// async_file.ts
import { ensureDir, readTextFile, writeTextFile } from "https://deno.land/std/fs/mod.ts";
import { delay } from "https://deno.land/std/async/mod.ts";

async function asyncFileOps() {
  try {
    await ensureDir("output"); // Ensure directory exists
    const content = await readTextFile("input.txt");
    console.log("Read content:", content);

    const newContent = "New content";
    await writeTextFile("output/output.txt", newContent);
    console.log("Wrote new content to output file.");

    await delay(2000); // Delay for 2 seconds
    console.log("Finished async operations.");
  } catch (err) {
    console.error("An error occurred:", err);
  }
}

asyncFileOps();
Enter fullscreen mode Exit fullscreen mode

Modules and Standard Library

Deno’s module system is based on ES Modules, allowing code import and export via URLs. Deno’s standard library provides useful modules for file system operations, networking, HTTP servers, JSON processing, encryption, and more.

Importing Standard Library Modules:

// import_std.ts
import { readTextFile } from "https://deno.land/std/fs/mod.ts";
import { serve } from "https://deno.land/std/http/server.ts";

// Read a file using readTextFile
const content = await readTextFile("example.txt");
console.log(content);

// Create an HTTP server
const s = serve({ port: 8000 });
console.log("Server is running on http://localhost:8000");

for await (const req of s) {
  req.respond({ status: 200, body: "Hello, World!\n" });
}
Enter fullscreen mode Exit fullscreen mode

Custom Modules:

// my_module.ts
export function add(a: number, b: number): number {
  return a + b;
}

// Import in another file
// import_ts.ts
import { add } from "./my_module.ts";

console.log(add(2, 3)); // Outputs 5
Enter fullscreen mode Exit fullscreen mode

JSON Processing in the Standard Library:

// json_example.ts
import { readTextFile } from "https://deno.land/std/fs/mod.ts";
import { json as parseJson } from "https://deno.land/std/json/mod.ts";

const jsonData = await readTextFile("data.json");
const data = parseJson(jsonData);
console.log(data);
Enter fullscreen mode Exit fullscreen mode

Network Operations in the Standard Library:

// net_example.ts
import { connect } from "https://deno.land/std/net/tcp.ts";

const conn = await connect({ hostname: "localhost", port: 8000 });
conn.write(new TextEncoder().encode("GET / HTTP/1.1\r\nHost: localhost:8000\r\n\r\n"));
const response = new TextDecoder().decode(await Deno.readAll(conn));
console.log(response);
conn.close();
Enter fullscreen mode Exit fullscreen mode

Using Third-Party Modules from deno.land/x:

// third_party_module.ts
import { log } from "https://x.nest.land/log@0.1.0/mod.ts";

log.info("This is an info message");
Enter fullscreen mode Exit fullscreen mode

Deno’s standard library and third-party modules are typically imported via HTTPS URLs, providing version control and security. deno.land is a module registry similar to npm but designed for Deno. x.nest.land is another Deno module repository hosting community-maintained modules.

Using WebSocket

Deno supports WebSocket through its standard library or third-party libraries. The following example uses the ws third-party library, popular for both Deno and Node.js.

Install the ws library:

deno install -A -f --unstable --name deno_ws https://deno.land/x/ws@v1.1.0/mod.ts
Enter fullscreen mode Exit fullscreen mode

Server-Side Code

Create a WebSocket server to listen for client connections and send messages to connected clients.

// server.ts
import { Server } from "deno_ws/mod.ts";

const server = new Server({ port: 8080 });

server.on("connection", (socket) => {
  console.log("Client connected");

  socket.on("message", (message) => {
    console.log(`Received message => ${message}`);
    socket.send(`You sent -> ${message}`);
  });

  socket.on("close", () => {
    console.log("Client disconnected");
  });
});

console.log("WebSocket server is running on ws://localhost:8080");
Enter fullscreen mode Exit fullscreen mode

Client-Side Code

Create a WebSocket client to connect to the server and send/receive messages.

// client.ts
import { connect } from "deno_ws/mod.ts";

const socket = connect("ws://localhost:8080");

socket.on("open", () => {
  console.log("Connected to WebSocket server");
  socket.send("Hello, Server!");
});

socket.on("message", (message) => {
  console.log(`Received from server: ${message}`);
});

socket.on("close", () => {
  console.log("Connection closed");
});
Enter fullscreen mode Exit fullscreen mode

Running the Example

Open two terminal windows.

In the first window, run the server:

deno run --allow-net server.ts
Enter fullscreen mode Exit fullscreen mode

In the second window, run the client:

deno run --allow-net client.ts
Enter fullscreen mode Exit fullscreen mode

Server-Side:

  • Imports the Server class and creates an instance listening on port 8080.
  • Handles new client connections with the connection event, logging and setting up a message handler.
  • Responds to each received message with a confirmation message.
  • Logs when a client disconnects via the close event.

Client-Side:

  • Uses the connect function to connect to the server’s URL.
  • Sets an open event handler to send a message when the connection is established.
  • Sets a message event handler to receive and log server messages.
  • Sets a close event handler to log connection closure.

Error Handling and Debugging

Error Handling

Deno uses try/catch for error handling, supporting asynchronous error handling.

// error_handling.ts
import { readFile } from "https://deno.land/std/fs/mod.ts";

try {
  const data = await readFile("non_existent_file.txt");
} catch (error) {
  if (error instanceof Deno.errors.NotFound) {
    console.error("File not found:", error);
  } else {
    throw error; // Re-throw unhandled errors
  }
}
Enter fullscreen mode Exit fullscreen mode

Debugging

Debugging in Deno can be done using console.log, console.error, and the debugger statement with an IDE or browser developer tools.

// debug.ts
function debugFunction(value) {
  debugger; // Pauses execution, allowing context inspection in a debugger
  console.log("Debugging value:", value);
}

debugFunction("Debug me");
Enter fullscreen mode Exit fullscreen mode

Performance Optimization

  1. Avoid Unnecessary Computations: Compute values only when needed, avoiding premature calculations.
  2. Use Asynchronous Operations: For I/O-intensive tasks, use async operations to avoid blocking the main thread.
  3. Cache Results: Cache results of repetitive computations.
  4. Leverage Type Checking: TypeScript’s type system helps prevent runtime errors and improves code quality.
  5. Restrict Permissions: Deno’s permission model allows precise control over code access, minimizing resource waste.

Here’s an optimization example using deno.cache to cache imported modules:

// optimized_import.ts
import { cache } from "https://deno.land/x/deno.land_std@0.125.0/cache/mod.ts";

const cachedModule = await cache(
  "https://deno.land/x/your_module@latest",
  ".cache",
);

// Import from cache
import * as mod from `${cachedModule}/mod.ts`;
Enter fullscreen mode Exit fullscreen mode

👉 Click to join and systematically improve development capabilities: Advanced Development Learning

Top comments (0)