Lesson 20-Deno Application Basics

Web Development Basics

HTTP Server Creation

Creating a Basic Server with Deno.serve
Deno 1.28+ introduced the simpler Deno.serve API, streamlining HTTP server creation:

// basic_server.ts
Deno.serve((req) => {
  return new Response("Hello from Deno!", {
    headers: { "Content-Type": "text/plain" }
  });
});

// Run: deno run --allow-net basic_server.ts

Basic Server with Routing

// router_server.ts
Deno.serve((req) => {
  const url = new URL(req.url);

  if (url.pathname === "/") {
    return new Response("Home Page");
  } else if (url.pathname === "/about") {
    return new Response("About Page");
  }

  return new Response("404 Not Found", { status: 404 });
});

// Run: deno run --allow-net router_server.ts

Routing and Request Handling

Middleware Mechanism Implementation

// middleware_server.ts
type Middleware = (req: Request, next: () => Promise<Response>) => Promise<Response>;

function createServer(middlewares: Middleware[]) {
  return async (req: Request) => {
    let index = -1;

    async function dispatch(i: number): Promise<Response> {
      if (i <= index) throw new Error("next() called multiple times");
      index = i;
      if (i >= middlewares.length) return new Response("Not Found", { status: 404 });
      return middlewares[i](req, () => dispatch(i + 1));
    }

    return dispatch(0);
  };
}

// Example middleware
async function loggerMiddleware(req: Request, next: () => Promise<Response>) {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  return next();
}

async function authMiddleware(req: Request, next: () => Promise<Response>) {
  const authHeader = req.headers.get("Authorization");
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    return new Response("Unauthorized", { status: 401 });
  }
  return next();
}

// Create server
const server = createServer([loggerMiddleware, authMiddleware]);

Deno.serve(server);

// Run: deno run --allow-net middleware_server.ts

Static File Serving

Configuring Static File Serving

// static_server.ts
Deno.serve({
  handler: (req) => {
    const url = new URL(req.url);
    const filePath = `./static${url.pathname}`;

    // Check if file exists
    try {
      const file = await Deno.open(filePath, { read: true });
      file.close();

      return new Response(Deno.readFile(filePath), {
        headers: { "Content-Type": getContentType(filePath) }
      });
    } catch {
      return new Response("404 Not Found", { status: 404 });
    }
  }
});

function getContentType(filePath: string): string {
  const ext = filePath.split(".").pop();
  switch (ext) {
    case "html": return "text/html";
    case "css": return "text/css";
    case "js": return "application/javascript";
    case "json": return "application/json";
    default: return "text/plain";
  }
}

// Run: deno run --allow-net --allow-read static_server.ts

Using Oak Framework’s Static File Middleware

// oak_static_server.ts
import { Application, oakStatic } from "https://deno.land/x/oak@v12.6.1/mod.ts";

const app = new Application();

// Static file middleware
app.use(oakStatic("./static", {
  extensions: ["html"],
  index: "index.html"
}));

app.use((ctx) => {
  if (ctx.request.url.pathname === "/api") {
    ctx.response.body = { message: "API Response" };
  }
});

console.log("Server running on http://localhost:8000");
await app.listen({ port: 8000 });

// Run: deno run --allow-net --allow-read oak_static_server.ts

Template Engine Integration

EJS Template Integration

// ejs_server.ts
import { Application } from "https://deno.land/x/oak@v12.6.1/mod.ts";
import { renderFile } from "https://deno.land/x/dejs@0.10.3/mod.ts";

const app = new Application();

app.use(async (ctx) => {
  if (ctx.request.url.pathname === "/") {
    const data = { title: "Deno EJS Demo", items: ["Item 1", "Item 2"] };
    ctx.response.body = await renderFile("./views/index.ejs", data);
    ctx.response.type = "text/html";
  }
});

console.log("Server running on http://localhost:8000");
await app.listen({ port: 8000 });

// Run: deno run --allow-net ejs_server.ts

View File Example (views/index.ejs)

<!DOCTYPE html>
<html>
<head>
  <title><%= title %></title>
</head>
<body>
  <h1><%= title %></h1>
  <ul>
    <% items.forEach(item => { %>
      <li><%= item %></li>
    <% }) %>
  </ul>
</body>
</html>

RESTful API Design and Implementation

Complete RESTful API Example

// rest_api_server.ts
import { Application, Router } from "https://deno.land/x/oak@v12.6.1/mod.ts";

interface User {
  id: number;
  name: string;
  email: string;
}

const users: User[] = [
  { id: 1, name: "Alice", email: "alice@example.com" },
  { id: 2, name: "Bob", email: "bob@example.com" }
];

const router = new Router();

// Get all users
router.get("/users", (ctx) => {
  ctx.response.body = users;
});

// Get single user
router.get("/users/:id", (ctx) => {
  const id = parseInt(ctx.params.id);
  const user = users.find(u => u.id === id);

  if (!user) {
    ctx.response.status = 404;
    ctx.response.body = { error: "User not found" };
    return;
  }

  ctx.response.body = user;
});

// Create user
router.post("/users", async (ctx) => {
  const body = await ctx.request.body({ type: "json" }).value;

  if (!body.name || !body.email) {
    ctx.response.status = 400;
    ctx.response.body = { error: "Name and email are required" };
    return;
  }

  const newUser: User = {
    id: users.length + 1,
    name: body.name,
    email: body.email
  };

  users.push(newUser);
  ctx.response.status = 201;
  ctx.response.body = newUser;
});

// Update user
router.put("/users/:id", async (ctx) => {
  const id = parseInt(ctx.params.id);
  const userIndex = users.findIndex(u => u.id === id);

  if (userIndex === -1) {
    ctx.response.status = 404;
    ctx.response.body = { error: "User not found" };
    return;
  }

  const body = await ctx.request.body({ type: "json" }).value;

  if (!body.name || !body.email) {
    ctx.response.status = 400;
    ctx.response.body = { error: "Name and email are required" };
    return;
  }

  users[userIndex] = {
    ...users[userIndex],
    name: body.name,
    email: body.email
  };

  ctx.response.body = users[userIndex];
});

// Delete user
router.delete("/users/:id", (ctx) => {
  const id = parseInt(ctx.params.id);
  const userIndex = users.findIndex(u => u.id === id);

  if (userIndex === -1) {
    ctx.response.status = 404;
    ctx.response.body = { error: "User not found" };
    return;
  }

  users.splice(userIndex, 1);
  ctx.response.status = 204;
});

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

console.log("REST API server running on http://localhost:8000");
await app.listen({ port: 8000 });

// Run: deno run --allow-net rest_api_server.ts

Database Interaction

MySQL Database Connection and Queries

Using mysql2 Module

// mysql_example.ts
import mysql from "https://deno.land/x/mysql2@v3.6.0/mod.ts";

// Create connection
const conn = await mysql.connect({
  hostname: "127.0.0.1",
  username: "root",
  password: "password",
  db: "testdb",
  port: 3306,
});

// Execute query
const result = await conn.query("SELECT * FROM users");
console.log(result);

// Insert data
const insertResult = await conn.execute(
  "INSERT INTO users (name, email) VALUES (?, ?)",
  ["Alice", "alice@example.com"]
);
console.log(insertResult);

// Close connection
await conn.close();

// Run: deno run --allow-net mysql_example.ts

PostgreSQL Database Operations

Using pg Module

// postgres_example.ts
import { Client } from "https://deno.land/x/postgres@v0.17.0/mod.ts";

// Create client
const client = new Client({
  user: "postgres",
  password: "password",
  database: "testdb",
  hostname: "127.0.0.1",
  port: 5432,
});

// Connect to database
await client.connect();

// Execute query
const result = await client.queryObject("SELECT * FROM users");
console.log(result.rows);

// Insert data
const insertResult = await client.queryObject(
  "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *",
  ["Bob", "bob@example.com"]
);
console.log(insertResult.rows);

// Close connection
await client.close();

// Run: deno run --allow-net postgres_example.ts

SQLite Database Operations

Using better-sqlite3 Module

// sqlite_example.ts
import Database from "https://deno.land/x/better_sqlite3@7.6.0/mod.ts";

// Create database connection
const db = new Database("test.db");

// Create table
db.query(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE
  )
`);

// Insert data
const insertStmt = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
insertStmt.run("Charlie", "charlie@example.com");

// Query data
const result = db.query("SELECT * FROM users");
for (const row of result) {
  console.log(row);
}

// Close database
db.close();

// Run: deno run --allow-write sqlite_example.ts

Database Connection Pooling and Performance Optimization

MySQL Connection Pool Example

// mysql_pool_example.ts
import mysql from "https://deno.land/x/mysql2@v3.6.0/mod.ts";

// Create connection pool
const pool = mysql.createPool({
  hostname: "127.0.0.1",
  username: "root",
  password: "password",
  db: "testdb",
  port: 3306,
  poolSize: 10, // Connection pool size
});

// Get connection from pool
const conn = await pool.getConnection();

try {
  // Execute query
  const result = await conn.query("SELECT * FROM users");
  console.log(result);
} finally {
  // Release connection back to pool
  conn.release();
}

// Close pool
await pool.end();

// Run: deno run --allow-net mysql_pool_example.ts

ORM and ODM Selection and Comparison

Prisma Client Example

// prisma_example.ts
import { PrismaClient } from "https://esm.sh/prisma@5.0.0/client";

// Create Prisma client
const prisma = new PrismaClient();

// Query users
const users = await prisma.user.findMany();
console.log(users);

// Create user
const newUser = await prisma.user.create({
  data: {
    name: "David",
    email: "david@example.com"
  }
});
console.log(newUser);

// Close client
await prisma.$disconnect();

// Run: deno run --allow-net prisma_example.ts

TypeORM Integration with Deno

// typeorm_example.ts
import "https://deno.land/x/typeorm@0.3.11/mod.ts";
import { User } from "./entities/user.ts";

// Create connection
const connection = await createConnection({
  type: "mysql",
  host: "127.0.0.1",
  port: 3306,
  username: "root",
  password: "password",
  database: "testdb",
  entities: [User],
  synchronize: true,
});

// Get Repository
const userRepository = connection.getRepository(User);

// Create user
const user = new User();
user.name = "Eve";
user.email = "eve@example.com";
await userRepository.save(user);

// Query users
const users = await userRepository.find();
console.log(users);

// Close connection
await connection.close();

// Run: deno run --allow-net typeorm_example.ts

Command-Line Tool Development

Command-Line Argument Parsing

Using flags Library for Argument Parsing

// cli_flags.ts
import { parse } from "https://deno.land/std@0.140.0/flags/mod.ts";

const args = parse(Deno.args);

if (args._.length === 0) {
  console.log("Usage: cli_flags.ts <command> [options]");
  Deno.exit(1);
}

const command = args._[0];
console.log("Command:", command);

if (args.help) {
  console.log("Help message");
  Deno.exit(0);
}

if (args.verbose) {
  console.log("Verbose mode enabled");
}

// Run: deno run --allow-read cli_flags.ts build --verbose --help

Building Complex CLI with yargs

// cli_yargs.ts
import yargs from "https://deno.land/x/yargs@17.6.2/deno.ts";

yargs(Deno.args)
  .scriptName("mycli")
  .command({
    command: "build",
    describe: "Build the project",
    handler: () => {
      console.log("Building project...");
    }
  })
  .option("verbose", {
    alias: "v",
    type: "boolean",
    description: "Run with verbose logging"
  })
  .help()
  .parse();

// Run: deno run --allow-read cli_yargs.ts build --verbose

File System Operations and Interaction

File Processing CLI Example

// cli_file_processor.ts
import { parse } from "https://deno.land/std@0.140.0/flags/mod.ts";
import { ensureFile, writeJson } from "https://deno.land/std@0.140.0/fs/mod.ts";

const args = parse(Deno.args);

if (args._.length === 0) {
  console.log("Usage: cli_file_processor.ts <file> [--json]");
  Deno.exit(1);
}

const filePath = args._[0];
const useJson = args.json || false;

try {
  // Check if file exists
  await Deno.stat(filePath);

  // Read file content
  const content = await Deno.readTextFile(filePath);

  if (useJson) {
    // Process as JSON
    try {
      const jsonData = JSON.parse(content);
      await ensureFile(`${filePath}.processed.json`);
      await writeJson(`${filePath}.processed.json`, jsonData);
      console.log("JSON file processed successfully");
    } catch {
      console.error("File is not valid JSON");
      Deno.exit(1);
    }
  } else {
    // Simple processing
    const processedContent = content.toUpperCase();
    await ensureFile(`${filePath}.processed.txt`);
    await Deno.writeTextFile(`${filePath}.processed.txt`, processedContent);
    console.log("Text file processed successfully");
  }
} catch (error) {
  if (error instanceof Deno.errors.NotFound) {
    console.error("File not found");
  } else {
    console.error("Error:", error);
  }
  Deno.exit(1);
}

// Run: deno run --allow-read --allow-write cli_file_processor.ts example.txt --json

Colored Output and Progress Bars

Using chalk and ora for Aesthetic Output

// cli_ui.ts
import chalk from "https://deno.land/x/chalk@v4.1.2/mod.ts";
import ora from "https://deno.land/x/ora@v5.1.0/mod.ts";

// Colored output example
console.log(chalk.blue("This is blue text"));
console.log(chalk.red.bold("This is red bold text"));
console.log(chalk.green("Success message"));

// Progress bar example
const spinner = ora("Loading...").start();

// Simulate async operation
setTimeout(() => {
  spinner.succeed("Loaded successfully!");
}, 2000);

// Run: deno run --allow-read cli_ui.ts

Scaffolding Tool Development

Simple Yeoman-Style Scaffolding

// scaffold_cli.ts
import { prompt } from "https://deno.land/x/cliffy@v0.25.0/prompt/mod.ts";
import { ensureDir, writeJson } from "https://deno.land/std@0.140.0/fs/mod.ts";
import chalk from "https://deno.land/x/chalk@v4.1.2/mod.ts";

interface ProjectConfig {
  name: string;
  description: string;
  author: string;
  useTypeScript: boolean;
}

async function createProject() {
  // Collect user input
  const config: ProjectConfig = await prompt([
    {
      type: "input",
      name: "name",
      message: "Project name:",
    },
    {
      type: "input",
      name: "description",
      message: "Project description:",
    },
    {
      type: "input",
      name: "author",
      message: "Author name:",
    },
    {
      type: "confirm",
      name: "useTypeScript",
      message: "Use TypeScript?",
      initial: true,
    },
  ]);

  // Create project directory
  await ensureDir(config.name);

  // Create package.json
  const packageJson = {
    name: config.name,
    version: "1.0.0",
    description: config.description,
    author: config.author,
    scripts: {
      start: config.useTypeScript 
        ? "deno run --allow-net src/index.ts"
        : "deno run --allow-net index.js",
    },
    dependencies: {
      deno: "latest",
    },
  };

  await writeJson(`${config.name}/package.json`, packageJson);

  // Create example file
  if (config.useTypeScript) {
    await ensureDir(`${config.name}/src`);
    await Deno.writeTextFile(
      `${config.name}/src/index.ts`,
      `console.log("Hello from ${config.name}!");`
    );
  } else {
    await Deno.writeTextFile(
      `${config.name}/index.js`,
      `console.log("Hello from ${config.name}!");`
    );
  }

  console.log(chalk.green(`Project ${config.name} created successfully!`));
}

createProject().catch(console.error);

// Run: deno run --allow-read --allow-write scaffold_cli.ts

Global vs. Local Installation

Global CLI Tool Installation

# Global installation (requires deno install)
deno install --allow-read --allow-write --allow-net -n mycli https://example.com/cli.ts

# Use globally installed tool
mycli build --verbose

Local CLI Tool Installation

# Install as dependency in project
deno cache --import-map=import_map.json cli.ts

# Add bin field in package.json (if using bundler)
{
  "bin": {
    "mycli": "cli.ts"
  }
}

# Local usage (via npx or project scripts)
deno run --allow-read --allow-write --allow-net cli.ts build --verbose

Key Differences

  1. Permission Scope: Global installation requires broader permissions.
  2. Environment Variables: Global tools need PATH environment configuration.
  3. Dependency Management: Local tools allow precise version control.
  4. Portability: Local tools are easier to migrate with projects.
  5. Update Mechanism: Global tools require manual updates.

Share your love