🎯 Ejemplos recomendados
Balanced sample collections from various categories for you to explore
Ejemplos de Deno Deploy
Ejemplos de la plataforma de computación de borde Deno Deploy incluyendo funciones serverless, APIs y aplicaciones en tiempo real con despliegue de configuración cero
💻 Hello World Básico typescript
🟢 simple
Introducción simple a aplicaciones 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);
},
};
💻 Comunicación en Tiempo Real WebSocket typescript
🟡 intermediate
Implementar funcionalidad de comunicación WebSocket en tiempo 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);
},
};
💻 Integración de Base de Datos typescript
🔴 complex
Conectar y operar bases de datos
// 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);
},
};