| name | deno |
| description | [Applies to: **/*.{js,jsx,ts,tsx}] This guide defines definitive Deno best practices for our team, focusing on secure, maintainable, and idiomatic code using Deno's built-in tooling and modern web standards. |
| source | cursor_mdc |
Deno Best Practices
Deno is our runtime of choice for its security, built-in tooling, and first-class TypeScript support. This guide outlines the definitive best practices for writing Deno applications, ensuring consistency, performance, and maintainability across our projects.
1. Project Configuration: deno.json is King
Always use deno.json (or deno.jsonc) as the single source of truth for project configuration. This file unifies TypeScript compiler options, Deno-specific flags, tasks, linting, and formatting. Avoid tsconfig.json for Deno-first projects.
✅ GOOD: Centralized deno.json
{
"compilerOptions": {
"target": "es2022",
"lib": ["deno.window", "deno.unstable"],
"strict": true,
"checkJs": true,
"jsx": "react-jsx"
},
"lint": {
"rules": {
"tags": ["recommended"]
}
},
"fmt": {
"options": {
"useTabs": false,
"lineWidth": 100,
"indentWidth": 2,
"singleQuote": true
}
},
"imports": {
"std/": "https://deno.land/std@0.224.0/",
"oak": "jsr:@oak/oak@16"
},
"tasks": {
"dev": "deno run --allow-net --watch main.ts",
"test": "deno test --allow-read",
"start": "deno run --allow-net main.ts"
}
}
❌ BAD: Fragmented Configuration
{
"compilerOptions": {
"target": "es2022",
"strict": true
}
}
{
"scripts": {
"dev": "deno run --allow-net --watch main.ts"
}
}
2. Module Organization: ES Modules & Explicit Imports
Deno embraces ES modules and URL-based dependencies. Always use explicit file extensions and prefer JSR or deno.land/x for external modules.
2.1. Explicit URL Imports
Import directly from URLs or use deno.json's imports map for cleaner paths.
✅ GOOD: URL Imports with Import Map
import { serve } from "std/http/server.ts";
import { Application } from "oak";
const app = new Application();
app.listen({ port: 8000 });
console.log("Server running on http://localhost:8000");
❌ BAD: Missing Extensions or Node.js-style Imports
import { serve } from "https://deno.land/std/http/server";
import { Application } from "@oak/oak";
2.2. Entry Point: mod.ts
For libraries or main application entry points, name the primary module mod.ts.
✅ GOOD: mod.ts as Entry
export * from "./src/utils.ts";
export * from "./src/server.ts";
❌ BAD: index.ts for Deno-first projects
export * from "./src/utils.ts";
3. Security: Explicit Permissions
Deno is secure by default. Always declare necessary permissions explicitly using --allow-* flags. Grant the minimal required permissions.
✅ GOOD: Minimal Permissions
deno run --allow-net=:8080 --allow-read=. main.ts
❌ BAD: Overly Permissive or Missing Permissions
deno run --allow-all main.ts
deno run main.ts
4. Standard Library Preference
Leverage Deno's built-in standard library (deno_std) for common tasks like HTTP, file system, and testing. This reduces external dependencies and maintains consistency.
✅ GOOD: Using deno_std
import { readAll } from "std/io/read_all.ts";
async function readFileContents(filePath: string): Promise<string> {
const file = await Deno.open(filePath);
const contents = await readAll(file);
Deno.close(file.rid);
return new TextDecoder().decode(contents);
}
❌ BAD: Relying on Third-Party for Basic Functionality
import { readFile } from "https://deno.land/x/fs_extra/mod.ts";
async function readFileContents(filePath: string): Promise<string> {
return await readFile(filePath, { encoding: "utf8" });
}
5. Type Safety: TypeScript First
Write code in TypeScript (.ts or .tsx). For JavaScript files, enable type checking with @ts-check and use JSDoc for type annotations. Ensure strict: true is set in deno.json.
✅ GOOD: TypeScript with Strict Checks
interface User {
id: string;
name: string;
email?: string;
}
function getUserName(user: User): string {
return user.name;
}
✅ GOOD: JSDoc for JavaScript
function calculateTotalPrice(products) {
return products.reduce((total, p) => total + p.price, 0);
}
❌ BAD: Untyped JavaScript or Relaxed TypeScript
function getUserName(user) {
return user.name;
}
6. Asynchronous Operations: Top-Level await
Embrace top-level await for cleaner asynchronous code, especially in entry files. Always await promises explicitly.
✅ GOOD: Top-Level await
import { serve } from "std/http/server.ts";
const handler = (req: Request): Response => {
return new Response("Hello from Deno!");
};
console.log("Server starting...");
await serve(handler, { port: 8000 });
console.log("Server stopped.");
❌ BAD: IIFEs or Unhandled Promises
import { serve } from "std/http/server.ts";
const handler = (req: Request): Response => {
return new Response("Hello from Deno!");
};
(async () => {
console.log("Server starting...");
serve(handler, { port: 8000 });
console.log("Server stopped.");
})();
7. Testing: Built-in deno test
Use Deno's built-in test runner (deno test) and deno_std/assert for writing unit and integration tests. Organize tests in _test.ts or _test.js files.
✅ GOOD: Idiomatic Deno Testing
import { assertEquals } from "std/assert/assert_equals.ts";
import { add } from "./math.ts";
Deno.test("add function sums two numbers correctly", () => {
assertEquals(add(1, 2), 3);
assertEquals(add(-1, 1), 0);
assertEquals(add(0, 0), 0);
});
export function add(a: number, b: number): number {
return a + b;
}
❌ BAD: Third-Party Test Runners or Inconsistent Naming
import { describe, it, expect } from "https://deno.land/x/some_test_lib/mod.ts";
import { add } from "./math.ts";
describe("add", () => {
it("should sum numbers", () => {
expect(add(1, 2)).toBe(3);
});
});