Deno’s file system operation API design reflects the security and asynchronous characteristics of modern JavaScript. Unlike Node.js, all file operations in Deno require explicit permissions by default, and it adopts a Promise-based asynchronous API design. This tutorial will provide an in-depth analysis of the core functionalities of Deno’s file system operations, demonstrating applications from basic to advanced scenarios through code examples.
Basic File Operations
Fundamentals of File Reading and Writing
Synchronous vs. Asynchronous Comparison
Deno provides both synchronous and asynchronous methods for file operations, but it is recommended to prioritize asynchronous APIs to avoid blocking the event loop:
// Asynchronous file reading (recommended)
const text = await Deno.readTextFile("example.txt");
console.log(text);
// Synchronous reading (not recommended)
const syncText = Deno.readTextFileSync("example.txt");Binary File Operations
For handling non-text files, use readFile and writeFile:
// Asynchronous reading and writing of binary files
const binaryData = await Deno.readFile("image.png");
await Deno.writeFile("copy.png", binaryData);File System Permission Control
Permission Flag Descriptions
Deno’s file operations require explicit declaration of permissions:
| Permission Flag | Purpose |
|---|---|
--allow-read | Allows file reading operations |
--allow-write | Allows file writing operations |
--allow-net | Allows network access (some file operations may require this) |
Minimal Permission Example
# Only allow reading from a specific directory
deno run --allow-read=/path/to/dir script.ts
# Allow writing to a specific file
deno run --allow-write=/path/to/file script.tsAdvanced File Operations
File System Status Checks
Retrieving File Metadata
const fileInfo = await Deno.stat("example.txt");
console.log({
size: fileInfo.size, // File size in bytes
isFile: fileInfo.isFile, // Whether it is a file
isDirectory: fileInfo.isDirectory, // Whether it is a directory
modified: fileInfo.mtime, // Modification time
created: fileInfo.birthtime // Creation time
});Checking Path Existence
const exists = await Deno.exists("example.txt");
console.log("Does the file exist:", exists);Directory Operations
Recursive Directory Traversal
async function walkDir(dirPath: string) {
const entries = await Deno.readDir(dirPath);
for await (const entry of entries) {
const fullPath = `${dirPath}/${entry.name}`;
if (entry.isDirectory) {
await walkDir(fullPath); // Recursively traverse subdirectories
} else {
console.log("File:", fullPath);
}
}
}
// Usage example
await walkDir("./documents");Directory Creation and Deletion
// Create a directory (including parent directories)
await Deno.mkdir("nested/dir", { recursive: true });
// Delete an empty directory
await Deno.remove("empty_dir");
// Recursively delete a directory
await Deno.remove("nested_dir", { recursive: true });Advanced File System Features
File Locking and Concurrency Control
Implementing File Locks
Deno does not provide a file lock API, but a simple lock mechanism can be implemented as follows:
async function withFileLock<T>(
filePath: string,
callback: () => Promise<T>
): Promise<T> {
const lockFile = `${filePath}.lock`;
try {
// Attempt to create the lock file (atomic operation)
await Deno.open(lockFile, { create: true, write: true });
// Execute critical code
return await callback();
} finally {
// Ensure the lock is released
await Deno.remove(lockFile);
}
}
// Usage example
await withFileLock("data.json", async () => {
const data = JSON.parse(await Deno.readTextFile("data.json"));
data.counter++;
await Deno.writeTextFile("data.json", JSON.stringify(data));
});File System Monitoring
Listening for File Changes
const watcher = Deno.watchFs(["./config"]);
(async () => {
for await (const event of watcher) {
console.log("File system event:", {
kind: event.kind, // create|modify|remove
paths: event.paths, // Affected paths
flag: event.flag // (Only supported on some systems)
});
if (event.kind === "modify" && event.paths[0].endsWith("config.json")) {
console.log("Configuration file updated, reloading...");
const config = JSON.parse(await Deno.readTextFile("config.json"));
// Handle configuration update...
}
}
})();
// Note: Requires --allow-read permissionPerformance Optimization Techniques
Handling Large Files
Streaming File Reading
async function processLargeFile(filePath: string) {
const file = await Deno.open(filePath, { read: true });
const reader = new Deno.Buffer();
// Read in chunks (to avoid memory overflow)
while (true) {
const bytesRead = await file.read(reader);
if (bytesRead === null) break; // End of file
const chunk = reader.bytes();
console.log("Processing data chunk:", chunk.length);
// Process the data chunk...
reader.reset(); // Reset the buffer
}
file.close();
}
// Usage example
await processLargeFile("large_data.bin");Optimizing Batch Operations
Batch File Processing Pattern
async function batchProcessFiles(filePaths: string[]) {
// Control concurrency
const concurrency = 3;
const semaphore = new Semaphore(concurrency);
await Promise.all(
filePaths.map(async (filePath) => {
await semaphore.acquire();
try {
await processSingleFile(filePath);
} finally {
semaphore.release();
}
})
);
}
// Simple semaphore implementation
class Semaphore {
private queue: Array<() => void> = [];
constructor(private count: number) {}
acquire() {
return new Promise<void>((resolve) => {
if (this.count > 0) {
this.count--;
resolve();
} else {
this.queue.push(resolve);
}
});
}
release() {
if (this.queue.length > 0) {
const next = this.queue.shift()!;
next();
} else {
this.count++;
}
}
}
// Usage example
await batchProcessFiles(["file1.txt", "file2.txt", /* ... */]);Security Best Practices
Principle of Least Privilege
Example of Precise Permission Control
# Only allow reading a specific file
deno run --allow-read=/path/to/specific/file script.ts
# Allow writing to a specific directory but not reading
deno run --allow-write=/path/to/write/dir --allow-read=/path/to/read/dir script.tsDynamic Permission Requests
async function safeFileRead(filePath: string) {
// Check current permissions
const readStatus = await Deno.permissions.query({ name: "read" });
if (readStatus.state === "granted") {
return Deno.readTextFile(filePath);
}
// Request permission to read a specific file
const granted = await Deno.permissions.request({
name: "read",
path: filePath
});
if (granted) {
return Deno.readTextFile(filePath);
}
throw new Error("File read permission denied");
}Security Protection Measures
Protection Against Path Traversal Attacks
function safeJoin(basePath: string, relativePath: string): string {
const resolvedBase = new URL(basePath).pathname;
const resolvedPath = new URL(relativePath, `file://${resolvedBase}/`).pathname;
// Prevent directory traversal attacks
if (!resolvedPath.startsWith(resolvedBase)) {
throw new Error("Illegal path access");
}
return resolvedPath;
}
// Usage example
const safePath = safeJoin("/safe/dir", "../malicious.txt"); // Throws an errorHandling Sensitive Files
async function handleSensitiveFile() {
// 1. Get the path from an environment variable (rather than hardcoding)
const filePath = Deno.env.get("SENSITIVE_FILE_PATH");
if (!filePath) throw new Error("Sensitive file path not configured");
// 2. Temporarily request permissions
const hasAccess = await Deno.permissions.request({ name: "read", path: filePath });
if (!hasAccess) throw new Error("Insufficient permissions");
try {
// 3. Process immediately and clear from memory
const content = await Deno.readTextFile(filePath);
const processed = processContent(content);
// 4. Clear sensitive data from memory promptly
content.replace(/[a-zA-Z0-9]/g, "*");
return processed;
} finally {
// 5. Explicitly release resources
// (Deno does not have explicit memory management, but ensure file handles are closed)
}
}Practical Examples
Configuration File Hot Reload System
class ConfigManager {
private config: any;
private watcher: Deno.FsWatcher;
private lastModified = 0;
constructor(private filePath: string) {
this.loadConfig();
this.setupWatcher();
}
private async loadConfig() {
const stats = await Deno.stat(this.filePath);
if (stats.mtime?.getTime() <= this.lastModified) return;
this.lastModified = stats.mtime.getTime();
const content = await Deno.readTextFile(this.filePath);
this.config = JSON.parse(content);
console.log("Configuration updated:", this.config);
}
private setupWatcher() {
this.watcher = Deno.watchFs(this.filePath);
(async () => {
for await (const event of this.watcher) {
if (event.kind === "modify") {
await this.loadConfig();
}
}
})();
}
getConfig() {
return this.config;
}
}
// Usage example
const configManager = new ConfigManager("./config.json");
setInterval(() => {
console.log("Current configuration:", configManager.getConfig());
}, 5000);High-Performance Logging System
class AsyncLogger {
private writeQueue: string[] = [];
private isWriting = false;
private filePath: string;
constructor(filePath: string) {
this.filePath = filePath;
}
async log(message: string) {
this.writeQueue.push(`${new Date().toISOString()} - ${message}\n`);
if (!this.isWriting) {
this.processQueue();
}
}
private async processQueue() {
if (this.writeQueue.length === 0) {
this.isWriting = false;
return;
}
this.isWriting = true;
const batch = this.writeQueue.splice(0, 100); // Write 100 entries in a batch
const content = batch.join("");
try {
await Deno.writeTextFile(this.filePath, content, { append: true });
} catch (error) {
console.error("Log write failed:", error);
// Retry logic...
} finally {
this.processQueue(); // Process the next batch
}
}
}
// Usage example
const logger = new AsyncLogger("./app.log");
setInterval(() => {
logger.log(`System status update: ${Math.random()}`);
}, 100);



