Exemplos Deno Deploy

Exemplos da plataforma de computação de borda Deno Deploy incluindo funções serverless, APIs e aplicações em tempo real com implantação de configuração zero

💻 Hello World Básico typescript

🟢 simple

Introdução simples a aplicações Deno Deploy

// Deno Deploy Hello World
// main.ts - Entry point for Deno Deploy application

import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { Router } from "https://deno.land/x/[email protected]/mod.ts";

// 1. Basic HTTP Server
async function basicHandler(request: Request): Promise<Response> {
  const url = new URL(request.url);
  const pathname = url.pathname;

  // Route handling
  switch (pathname) {
    case "/":
      return new Response(
        JSON.stringify({
          message: "Hello from Deno Deploy! 🦕",
          timestamp: new Date().toISOString(),
          method: request.method,
          url: request.url,
        }),
        {
          status: 200,
          headers: {
            "Content-Type": "application/json",
            "Cache-Control": "public, max-age=3600",
          },
        }
      );

    case "/health":
      return new Response(
        JSON.stringify({
          status: "healthy",
          timestamp: new Date().toISOString(),
          uptime: performance.now(),
        }),
        {
          status: 200,
          headers: { "Content-Type": "application/json" },
        }
      );

    case "/echo":
      if (request.method === "POST") {
        const body = await request.text();
        return new Response(
          JSON.stringify({
            echo: body,
            method: request.method,
            headers: Object.fromEntries(request.headers.entries()),
          }),
          {
            status: 200,
            headers: { "Content-Type": "application/json" },
          }
        );
      } else {
        return new Response("Method not allowed", { status: 405 });
      }

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

// 2. Oak Router Example
const router = new Router();

router.get("/", (ctx) => {
  ctx.response.body = {
    message: "Hello from Deno Deploy with Oak Router! 🦕",
    framework: "Oak",
    timestamp: new Date().toISOString(),
  };
});

router.get("/api/users", (ctx) => {
  ctx.response.body = {
    users: [
      { id: 1, name: "Alice", email: "[email protected]" },
      { id: 2, name: "Bob", email: "[email protected]" },
    ],
    total: 2,
  };
});

router.get("/api/users/:id", (ctx) => {
  const id = ctx.params.id;
  ctx.response.body = {
    user: { id, name: `User ${id}`, email: `user${id}@example.com` },
  };
});

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

  ctx.response.body = {
    message: "User created successfully",
    user: { id: Date.now(), ...user },
  };
  ctx.response.status = 201;
});

// 3. Middleware Example
async function corsMiddleware(request: Request, next: () => Promise<Response>): Promise<Response> {
  const response = await next();

  // Add CORS headers
  response.headers.set("Access-Control-Allow-Origin", "*");
  response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");

  return response;
}

async function loggingMiddleware(request: Request, next: () => Promise<Response>): Promise<Response> {
  const start = Date.now();
  const response = await next();
  const duration = Date.now() - start;

  console.log(`${request.method} ${request.url} - ${response.status} - ${duration}ms`);

  response.headers.set("X-Response-Time", `${duration}ms`);

  return response;
}

// 4. Advanced Handler with Middleware Chain
async function advancedHandler(request: Request): Promise<Response> {
  // Create middleware chain
  const middleware = [
    corsMiddleware,
    loggingMiddleware,
  ];

  let response: Response;
  for (const middleware of middleware) {
    response = await middleware(request, () => {
      return router.routes()(request);
    });
  }

  return response!;
}

// 5. Environment Variables and Configuration
interface Env {
  API_KEY?: string;
  DATABASE_URL?: string;
  DEBUG?: string;
}

function getConfig(): Env {
  return {
    API_KEY: Deno.env.get("API_KEY"),
    DATABASE_URL: Deno.env.get("DATABASE_URL"),
    DEBUG: Deno.env.get("DEBUG"),
  };
}

// 6. Error Handling
class AppError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
    this.name = "AppError";
  }
}

async function errorHandler(request: Request, next: () => Promise<Response>): Promise<Response> {
  try {
    return await next();
  } catch (error) {
    console.error("Error handling request:", error);

    if (error instanceof AppError) {
      return new Response(
        JSON.stringify({ error: error.message }),
        {
          status: error.statusCode,
          headers: { "Content-Type": "application/json" },
        }
      );
    }

    return new Response(
      JSON.stringify({ error: "Internal Server Error" }),
      {
        status: 500,
        headers: { "Content-Type": "application/json" },
      }
    );
  }
}

// 7. Main Server Function
async function handler(request: Request): Promise<Response> {
  const config = getConfig();

  // Add config to request headers for debugging
  if (config.DEBUG === "true") {
    console.log("Request received:", request.method, request.url);
    console.log("Environment:", config);
  }

  return errorHandler(request, () => advancedHandler(request));
}

// 8. Start server
if (import.meta.main) {
  console.log("Starting Deno Deploy server...");
  await handler(new Request("https://example.com/"));
}

// Export for Deno Deploy
export default {
  async fetch(request: Request): Promise<Response> {
    return handler(request);
  },
};

💻 Comunicação em Tempo Real WebSocket typescript

🟡 intermediate

Implementar funcionalidade de comunicação WebSocket em tempo real

// Deno Deploy WebSocket Chat Application
// websocket.ts - Real-time chat with WebSocket support

import { serve } from "https://deno.land/[email protected]/http/server.ts";

// 1. WebSocket Connection Manager
interface Connection {
  id: string;
  ws: WebSocket;
  room: string;
  username: string;
  joinedAt: Date;
}

class ConnectionManager {
  private connections = new Map<string, Connection>();
  private rooms = new Map<string, Set<string>>();

  addConnection(connection: Connection): void {
    this.connections.set(connection.id, connection);

    // Add to room
    if (!this.rooms.has(connection.room)) {
      this.rooms.set(connection.room, new Set());
    }
    this.rooms.get(connection.room)!.add(connection.id);

    console.log(`Connection ${connection.id} joined room ${connection.room}`);
  }

  removeConnection(connectionId: string): void {
    const connection = this.connections.get(connectionId);
    if (connection) {
      // Remove from room
      const room = this.rooms.get(connection.room);
      if (room) {
        room.delete(connectionId);
        if (room.size === 0) {
          this.rooms.delete(connection.room);
        }
      }

      this.connections.delete(connectionId);
      console.log(`Connection ${connectionId} removed`);
    }
  }

  getRoomConnections(room: string): Connection[] {
    const roomConnections = this.rooms.get(room);
    if (!roomConnections) return [];

    return Array.from(roomConnections)
      .map(id => this.connections.get(id))
      .filter(Boolean) as Connection[];
  }

  broadcastToRoom(room: string, message: any, excludeId?: string): void {
    const connections = this.getRoomConnections(room);

    connections.forEach(connection => {
      if (connection.id !== excludeId) {
        connection.ws.send(JSON.stringify(message));
      }
    });
  }

  getRoomStats(): Record<string, number> {
    const stats: Record<string, number> = {};
    this.rooms.forEach((connections, room) => {
      stats[room] = connections.size;
    });
    return stats;
  }
}

// 2. Message Types
interface Message {
  type: 'join' | 'leave' | 'chat' | 'user_list' | 'room_stats';
  data: any;
}

interface ChatMessage {
  id: string;
  username: string;
  content: string;
  timestamp: string;
  room: string;
}

// 3. Initialize connection manager
const connectionManager = new ConnectionManager();

// 4. Message Handlers
async function handleMessage(connection: Connection, message: Message): Promise<void> {
  switch (message.type) {
    case 'chat':
      await handleChatMessage(connection, message.data);
      break;

    case 'user_list':
      await sendUserList(connection);
      break;

    case 'room_stats':
      await sendRoomStats(connection);
      break;

    default:
      console.log(`Unknown message type: ${message.type}`);
  }
}

async function handleChatMessage(connection: Connection, data: { content: string }): Promise<void> {
  const chatMessage: ChatMessage = {
    id: crypto.randomUUID(),
    username: connection.username,
    content: data.content,
    timestamp: new Date().toISOString(),
    room: connection.room,
  };

  // Validate message
  if (!chatMessage.content.trim()) {
    connection.ws.send(JSON.stringify({
      type: 'error',
      data: { message: 'Message content cannot be empty' }
    }));
    return;
  }

  // Broadcast to room
  connectionManager.broadcastToRoom(connection.room, {
    type: 'chat',
    data: chatMessage,
  });
}

async function sendUserList(connection: Connection): Promise<void> {
  const connections = connectionManager.getRoomConnections(connection.room);
  const users = connections.map(conn => ({
    id: conn.id,
    username: conn.username,
    joinedAt: conn.joinedAt,
  }));

  connection.ws.send(JSON.stringify({
    type: 'user_list',
    data: { users },
  }));
}

async function sendRoomStats(connection: Connection): Promise<void> {
  const stats = connectionManager.getRoomStats();
  const connections = connectionManager.getRoomConnections(connection.room);

  connection.ws.send(JSON.stringify({
    type: 'room_stats',
    data: {
      totalUsers: connections.length,
      roomStats: stats,
    },
  }));
}

// 5. WebSocket Handler
async function handleWebSocket(request: Request): Promise<Response> {
  const { socket, response } = Deno.upgradeWebSocket(request, {
    protocol: "chat-v1",
  });

  let connection: Connection | null = null;

  // Handle WebSocket messages
  socket.onopen = () => {
    console.log("WebSocket connection opened");
  };

  socket.onmessage = async (event) => {
    try {
      const message: Message = JSON.parse(event.data);

      if (message.type === 'join') {
        // Handle room join
        const { room, username } = message.data;

        if (!room || !username) {
          socket.send(JSON.stringify({
            type: 'error',
            data: { message: 'Room and username are required' }
          }));
          return;
        }

        connection = {
          id: crypto.randomUUID(),
          ws: socket,
          room,
          username,
          joinedAt: new Date(),
        };

        connectionManager.addConnection(connection);

        // Send join confirmation
        socket.send(JSON.stringify({
          type: 'join_success',
          data: {
            roomId: connection.room,
            userId: connection.id,
          },
        }));

        // Notify others in room
        connectionManager.broadcastToRoom(connection.room, {
          type: 'user_joined',
          data: {
            id: connection.id,
            username: connection.username,
          },
        }, connection.id);

        // Send current user list
        await sendUserList(connection);

        console.log(`User ${username} joined room ${room}`);
      } else if (connection) {
        // Handle other messages
        await handleMessage(connection, message);
      }
    } catch (error) {
      console.error("Error handling WebSocket message:", error);
      socket.send(JSON.stringify({
        type: 'error',
        data: { message: 'Invalid message format' }
      }));
    }
  };

  socket.onclose = () => {
    if (connection) {
      // Notify others in room
      connectionManager.broadcastToRoom(connection.room, {
        type: 'user_left',
        data: {
          id: connection.id,
          username: connection.username,
        },
      });

      connectionManager.removeConnection(connection.id);
      console.log(`WebSocket connection closed for ${connection.username}`);
    }
  };

  socket.onerror = (error) => {
    console.error("WebSocket error:", error);
  };

  return response;
}

// 6. HTTP API Endpoints
async function handleRequest(request: Request): Promise<Response> {
  const url = new URL(request.url);
  const pathname = url.pathname;

  switch (pathname) {
    case "/":
      return new Response(getHomePage(), {
        headers: { "Content-Type": "text/html" },
      });

    case "/api/stats":
      return new Response(
        JSON.stringify({
          totalConnections: connectionManager.getRoomStats(),
          timestamp: new Date().toISOString(),
        }),
        {
          headers: { "Content-Type": "application/json" },
        }
      );

    case "/api/rooms":
      return new Response(
        JSON.stringify({
          rooms: Object.entries(connectionManager.getRoomStats()),
        }),
        {
          headers: { "Content-Type": "application/json" },
        }
      );

    case "/ws":
      if (request.headers.get("upgrade") !== "websocket") {
        return new Response("Expected websocket connection", { status: 426 });
      }
      return handleWebSocket(request);

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

// 7. Simple HTML Client
function getHomePage(): string {
  return `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Deno Deploy WebSocket Chat</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .container { border: 1px solid #ccc; border-radius: 5px; padding: 20px; }
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; font-weight: bold; }
        input, select, textarea { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 3px; box-sizing: border-box; }
        button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 3px; cursor: pointer; }
        button:hover { background: #0056b3; }
        .chat-container { margin-top: 20px; border: 1px solid #ccc; border-radius: 5px; height: 300px; overflow-y: auto; padding: 10px; }
        .message { margin-bottom: 10px; padding: 5px; border-radius: 3px; }
        .user-message { background: #e3f2fd; }
        .system-message { background: #f5f5f5; font-style: italic; }
        .connected { color: green; }
        .disconnected { color: red; }
    </style>
</head>
<body>
    <h1>🦕 Deno Deploy WebSocket Chat</h1>

    <div class="container">
        <div id="connection-status" class="disconnected">Disconnected</div>

        <div id="join-form">
            <div class="form-group">
                <label for="username">Username:</label>
                <input type="text" id="username" placeholder="Enter your username">
            </div>

            <div class="form-group">
                <label for="room">Room:</label>
                <select id="room">
                    <option value="general">General</option>
                    <option value="tech">Tech Talk</option>
                    <option value="random">Random</option>
                </select>
            </div>

            <button onclick="joinRoom()">Join Room</button>
        </div>

        <div id="chat-container" style="display: none;">
            <div class="chat-container" id="messages"></div>

            <div class="form-group">
                <textarea id="message-input" placeholder="Type your message..." rows="3"></textarea>
            </div>

            <button onclick="sendMessage()">Send Message</button>
            <button onclick="leaveRoom()">Leave Room</button>
        </div>
    </div>

    <script>
        let ws = null;
        let currentUser = null;
        let currentRoom = null;

        function joinRoom() {
            const username = document.getElementById('username').value.trim();
            const room = document.getElementById('room').value;

            if (!username) {
                alert('Please enter a username');
                return;
            }

            currentUser = username;
            currentRoom = room;

            // Create WebSocket connection
            ws = new WebSocket(`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`);

            ws.onopen = function() {
                document.getElementById('connection-status').className = 'connected';
                document.getElementById('connection-status').textContent = 'Connected';

                ws.send(JSON.stringify({
                    type: 'join',
                    data: { username, room }
                }));
            };

            ws.onmessage = function(event) {
                const message = JSON.parse(event.data);
                handleWebSocketMessage(message);
            };

            ws.onclose = function() {
                document.getElementById('connection-status').className = 'disconnected';
                document.getElementById('connection-status').textContent = 'Disconnected';
                document.getElementById('join-form').style.display = 'block';
                document.getElementById('chat-container').style.display = 'none';
            };

            ws.onerror = function(error) {
                console.error('WebSocket error:', error);
                alert('Connection error. Please try again.');
            };
        }

        function handleWebSocketMessage(message) {
            const messagesContainer = document.getElementById('messages');

            switch (message.type) {
                case 'join_success':
                    document.getElementById('join-form').style.display = 'none';
                    document.getElementById('chat-container').style.display = 'block';
                    break;

                case 'chat':
                    const chatMessage = message.data;
                    addMessage(`${chatMessage.username}: ${chatMessage.content}`, 'user-message');
                    break;

                case 'user_joined':
                    addMessage(`${message.data.username} joined the room`, 'system-message');
                    break;

                case 'user_left':
                    addMessage(`${message.data.username} left the room`, 'system-message');
                    break;

                case 'error':
                    addMessage(`Error: ${message.data.message}`, 'system-message');
                    break;
            }
        }

        function addMessage(content, className = '') {
            const messagesContainer = document.getElementById('messages');
            const messageDiv = document.createElement('div');
            messageDiv.className = `message ${className}`;
            messageDiv.textContent = content;
            messagesContainer.appendChild(messageDiv);
            messagesContainer.scrollTop = messagesContainer.scrollHeight;
        }

        function sendMessage() {
            const input = document.getElementById('message-input');
            const content = input.value.trim();

            if (content && ws && ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({
                    type: 'chat',
                    data: { content }
                }));
                input.value = '';
            }
        }

        function leaveRoom() {
            if (ws) {
                ws.close();
            }
        }

        // Handle Enter key in message input
        document.getElementById('message-input').addEventListener('keypress', function(e) {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                sendMessage();
            }
        });
    </script>
</body>
</html>
  `;
}

// 8. Main Server
export default {
  async fetch(request: Request): Promise<Response> {
    return handleRequest(request);
  },
};

💻 Integração de Banco de Dados typescript

🔴 complex

Conectar e operar bancos de dados

// Deno Deploy Database Integration
// database.ts - KV storage, PostgreSQL, and connection management

import { serve } from "https://deno.land/[email protected]/http/server.ts";

// 1. KV Storage (Deno KV)
interface User {
  id: string;
  username: string;
  email: string;
  createdAt: string;
  updatedAt: string;
}

class KVStorage {
  constructor(private kv: Deno.Kv) {}

  async setUser(user: User): Promise<void> {
    const key = ["users", user.id];
    const emailKey = ["users_by_email", user.email];

    const entry = {
      value: user,
      expiration: null, // Never expire
    };

    await this.kv.set(key, entry);
    await this.kv.set(emailKey, { userId: user.id });
  }

  async getUser(id: string): Promise<User | null> {
    const result = await this.kv.get(["users", id]);
    return result.value as User | null;
  }

  async getUserByEmail(email: string): Promise<User | null> {
    const result = await this.kv.get(["users_by_email", email]);
    if (!result.value) return null;

    const userId = (result.value as { userId: string }).userId;
    return this.getUser(userId);
  }

  async updateUser(id: string, updates: Partial<User>): Promise<User | null> {
    const user = await this.getUser(id);
    if (!user) return null;

    const updatedUser = {
      ...user,
      ...updates,
      updatedAt: new Date().toISOString(),
    };

    await this.setUser(updatedUser);
    return updatedUser;
  }

  async deleteUser(id: string): Promise<boolean> {
    const user = await this.getUser(id);
    if (!user) return false;

    await this.kv.delete(["users", id]);
    await this.kv.delete(["users_by_email", user.email]);
    return true;
  }

  async listUsers(limit: number = 50, cursor?: string): Promise<{ users: User[]; cursor?: string }> {
    const iterator = this.kv.list<User>({ prefix: ["users"] }, {
      limit,
      cursor,
    });

    const users: User[] = [];
    for await (const entry of iterator) {
      users.push(entry.value);
    }

    return {
      users,
      cursor: iterator.cursor,
    };
  }
}

// 2. PostgreSQL Connection Pool
interface PostgresConfig {
  hostname: string;
  port: number;
  database: string;
  user: string;
  password: string;
}

class PostgresConnection {
  private connection: any; // postgres.js connection

  constructor(private config: PostgresConfig) {}

  async connect(): Promise<void> {
    // Import postgres.js dynamically
    const postgres = await import("https://deno.land/x/[email protected]/mod.js");

    this.connection = postgres({
      host: this.config.hostname,
      port: this.config.port,
      database: this.config.database,
      user: this.config.user,
      password: this.config.password,
      ssl: 'require',
      max: 10, // Connection pool size
    });

    // Test connection
    await this.connection`SELECT NOW()`;
  }

  async query<T = any>(sql: string, params?: any[]): Promise<T[]> {
    try {
      const result = await this.connection.unsafe(sql, params);
      return result;
    } catch (error) {
      console.error("PostgreSQL query error:", error);
      throw error;
    }
  }

  async transaction<T>(callback: (sql: any) => Promise<T>): Promise<T> {
    return await this.connection.begin(callback);
  }

  async close(): Promise<void> {
    if (this.connection) {
      await this.connection.end();
    }
  }
}

// 3. ORM-like Interface
interface Model {
  id: string;
  createdAt: string;
  updatedAt: string;
}

abstract class BaseModel<T extends Model> {
  abstract tableName: string;

  constructor(protected db: PostgresConnection) {}

  abstract create(data: Omit<T, 'id' | 'createdAt' | 'updatedAt'>): Promise<T>;
  abstract findById(id: string): Promise<T | null>;
  abstract update(id: string, data: Partial<T>): Promise<T | null>;
  abstract delete(id: string): Promise<boolean>;
  abstract findAll(limit?: number, offset?: number): Promise<T[]>;

  protected generateId(): string {
    return crypto.randomUUID();
  }

  protected now(): string {
    return new Date().toISOString();
  }
}

// 4. User Model with PostgreSQL
interface Post extends Model {
  title: string;
  content: string;
  authorId: string;
  published: boolean;
}

class PostModel extends BaseModel<Post> {
  tableName = "posts";

  async create(data: Omit<Post, 'id' | 'createdAt' | 'updatedAt'>): Promise<Post> {
    const id = this.generateId();
    const now = this.now();

    const result = await this.db.query<Post[]>(
      `INSERT INTO posts (id, title, content, author_id, published, created_at, updated_at)
       VALUES ($1, $2, $3, $4, $5, $6, $7)
       RETURNING *`,
      [id, data.title, data.content, data.authorId, data.published, now, now]
    );

    return result[0];
  }

  async findById(id: string): Promise<Post | null> {
    const result = await this.db.query<Post[]>(
      "SELECT * FROM posts WHERE id = $1",
      [id]
    );
    return result[0] || null;
  }

  async update(id: string, data: Partial<Post>): Promise<Post | null> {
    const now = this.now();

    const result = await this.db.query<Post[]>(
      `UPDATE posts
       SET title = COALESCE($2, title),
           content = COALESCE($3, content),
           published = COALESCE($4, published),
           updated_at = $5
       WHERE id = $1
       RETURNING *`,
      [id, data.title, data.content, data.published, now]
    );

    return result[0] || null;
  }

  async delete(id: string): Promise<boolean> {
    const result = await this.db.query(
      "DELETE FROM posts WHERE id = $1",
      [id]
    );
    return result.count > 0;
  }

  async findAll(limit: number = 50, offset: number = 0): Promise<Post[]> {
    return await this.db.query<Post[]>(
      "SELECT * FROM posts ORDER BY created_at DESC LIMIT $1 OFFSET $2",
      [limit, offset]
    );
  }

  async findByAuthor(authorId: string): Promise<Post[]> {
    return await this.db.query<Post[]>(
      "SELECT * FROM posts WHERE author_id = $1 ORDER BY created_at DESC",
      [authorId]
    );
  }

  async search(query: string, limit: number = 20): Promise<Post[]> {
    return await this.db.query<Post[]>(
      `SELECT * FROM posts
       WHERE title ILIKE $1 OR content ILIKE $1
       ORDER BY created_at DESC
       LIMIT $2`,
      [`%${query}%`, limit]
    );
  }
}

// 5. Cache Layer with KV
class CacheLayer {
  constructor(private kv: Deno.Kv) {}

  private getCacheKey(type: string, id: string): string[] {
    return ["cache", type, id];
  }

  async get<T>(type: string, id: string): Promise<T | null> {
    const result = await this.kv.get<T>(this.getCacheKey(type, id));
    return result.value;
  }

  async set<T>(type: string, id: string, value: T, ttl: number = 3600): Promise<void> {
    await this.kv.set(this.getCacheKey(type, id), value, { expirationIn: ttl * 1000 });
  }

  async invalidate(type: string, id: string): Promise<void> {
    await this.kv.delete(this.getCacheKey(type, id));
  }

  async invalidatePattern(pattern: string[]): Promise<void> {
    const keys = [];
    for await (const entry of this.kv.list({ prefix: ["cache", ...pattern] })) {
      keys.push(entry.key);
    }

    await this.kv.deleteMany(keys);
  }
}

// 6. Repository Pattern
class PostRepository {
  constructor(
    private postModel: PostModel,
    private cache: CacheLayer
  ) {}

  async create(data: Omit<Post, 'id' | 'createdAt' | 'updatedAt'>): Promise<Post> {
    const post = await this.postModel.create(data);

    // Cache the new post
    await this.cache.set("posts", post.id, post);

    // Invalidate list cache
    await this.cache.invalidatePattern(["posts", "list"]);

    return post;
  }

  async findById(id: string, useCache: boolean = true): Promise<Post | null> {
    if (useCache) {
      const cached = await this.cache.get<Post>("posts", id);
      if (cached) return cached;
    }

    const post = await this.postModel.findById(id);

    if (post && useCache) {
      await this.cache.set("posts", id, post);
    }

    return post;
  }

  async update(id: string, data: Partial<Post>): Promise<Post | null> {
    const post = await this.postModel.update(id, data);

    if (post) {
      // Update cache
      await this.cache.set("posts", id, post);

      // Invalidate list cache
      await this.cache.invalidatePattern(["posts", "list"]);
    }

    return post;
  }

  async delete(id: string): Promise<boolean> {
    const success = await this.postModel.delete(id);

    if (success) {
      // Remove from cache
      await this.cache.invalidate("posts", id);

      // Invalidate list cache
      await this.cache.invalidatePattern(["posts", "list"]);
    }

    return success;
  }

  async findAll(limit: number = 50, offset: number = 0, useCache: boolean = true): Promise<Post[]> {
    const cacheKey = `list_${limit}_${offset}`;

    if (useCache) {
      const cached = await this.cache.get<Post[]>("posts", cacheKey);
      if (cached) return cached;
    }

    const posts = await this.postModel.findAll(limit, offset);

    if (useCache) {
      await this.cache.set("posts", cacheKey, posts, 600); // 10 minutes cache
    }

    return posts;
  }

  async search(query: string, limit: number = 20): Promise<Post[]> {
    const cacheKey = `search_${query}_${limit}`;

    // Don't cache search results for long
    const cached = await this.cache.get<Post[]>("posts", cacheKey);
    if (cached) return cached;

    const posts = await this.postModel.search(query, limit);
    await this.cache.set("posts", cacheKey, posts, 300); // 5 minutes cache

    return posts;
  }
}

// 7. Database Initialization
async function initializeDatabase(db: PostgresConnection): Promise<void> {
  const migrations = [
    `CREATE TABLE IF NOT EXISTS posts (
      id UUID PRIMARY KEY,
      title VARCHAR(255) NOT NULL,
      content TEXT NOT NULL,
      author_id UUID NOT NULL,
      published BOOLEAN DEFAULT FALSE,
      created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
      updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
    )`,

    `CREATE INDEX IF NOT EXISTS idx_posts_author_id ON posts(author_id)`,
    `CREATE INDEX IF NOT EXISTS idx_posts_created_at ON posts(created_at)`,
    `CREATE INDEX IF NOT EXISTS idx_posts_published ON posts(published)`,
    `CREATE INDEX IF NOT EXISTS idx_posts_search ON posts USING gin(to_tsvector('english', title || ' ' || content))`,
  ];

  for (const migration of migrations) {
    await db.query(migration);
  }

  console.log("Database initialized successfully");
}

// 8. HTTP API with Database
async function handleRequest(request: Request): Promise<Response> {
  const url = new URL(request.url);
  const pathname = url.pathname;
  const method = request.method;

  // Initialize database connections
  const kv = await Deno.openKv();
  const kvStorage = new KVStorage(kv);

  const pgConfig: PostgresConfig = {
    hostname: Deno.env.get("DB_HOST") || "localhost",
    port: parseInt(Deno.env.get("DB_PORT") || "5432"),
    database: Deno.env.get("DB_NAME") || "deno_deploy",
    user: Deno.env.get("DB_USER") || "postgres",
    password: Deno.env.get("DB_PASSWORD") || "",
  };

  const postgres = new PostgresConnection(pgConfig);
  await postgres.connect();

  await initializeDatabase(postgres);

  const postModel = new PostModel(postgres);
  const cache = new CacheLayer(kv);
  const postRepository = new PostRepository(postModel, cache);

  try {
    // API Routes
    if (pathname === "/api/posts" && method === "GET") {
      const limit = parseInt(url.searchParams.get("limit") || "50");
      const offset = parseInt(url.searchParams.get("offset") || "0");
      const posts = await postRepository.findAll(limit, offset);

      return new Response(JSON.stringify({ posts, total: posts.length }), {
        headers: { "Content-Type": "application/json" },
      });
    }

    if (pathname === "/api/posts" && method === "POST") {
      const body = await request.json();
      const post = await postRepository.create(body);

      return new Response(JSON.stringify(post), {
        status: 201,
        headers: { "Content-Type": "application/json" },
      });
    }

    if (pathname.startsWith("/api/posts/") && method === "GET") {
      const id = pathname.split("/")[3];
      const post = await postRepository.findById(id);

      if (!post) {
        return new Response("Post not found", { status: 404 });
      }

      return new Response(JSON.stringify(post), {
        headers: { "Content-Type": "application/json" },
      });
    }

    // KV Storage examples
    if (pathname === "/api/users" && method === "POST") {
      const { username, email } = await request.json();
      const user: User = {
        id: crypto.randomUUID(),
        username,
        email,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
      };

      await kvStorage.setUser(user);

      return new Response(JSON.stringify(user), {
        status: 201,
        headers: { "Content-Type": "application/json" },
      });
    }

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

  } finally {
    await postgres.close();
  }
}

// 9. Export for Deno Deploy
export default {
  async fetch(request: Request): Promise<Response> {
    return handleRequest(request);
  },
};