Deno Deploy enables you to run JavaScript, TypeScript, and WebAssembly at the edge, providing a modern and secure runtime environment. Let's explore how to build and deploy applications using this powerful platform.
Getting Started with Deno Deploy
Deno Deploy combines the security and simplicity of Deno with edge computing capabilities. Let's dive into creating your first application.
Basic HTTP Server
Create a simple HTTP server:
import { serve } from "https://deno.land/std/http/server.ts"
async function handler(req: Request): Promise<Response> {
const { pathname } = new URL(req.url)
if (pathname === "/api/hello") {
return new Response(JSON.stringify({
message: "Hello from Deno Deploy!"
}), {
headers: {
"content-type": "application/json"
}
})
}
return new Response("Not Found", { status: 404 })
}
serve(handler)
Working with Environment Variables
Manage configuration securely:
const API_KEY = Deno.env.get("API_KEY")
const ENVIRONMENT = Deno.env.get("ENVIRONMENT") || "development"
async function handleRequest(req: Request): Promise<Response> {
if (!API_KEY) {
throw new Error("API_KEY is required")
}
// Use environment variables securely
const headers = new Headers({
"X-Environment": ENVIRONMENT,
"X-Powered-By": "Deno Deploy"
})
return new Response("Configuration loaded", { headers })
}
Database Integration
Connect to databases using Deno's built-in drivers:
import { Client } from "https://deno.land/x/postgres/mod.ts"
const client = new Client({
user: Deno.env.get("DB_USER"),
database: Deno.env.get("DB_NAME"),
hostname: Deno.env.get("DB_HOST"),
password: Deno.env.get("DB_PASSWORD"),
port: 5432
})
async function getUserData(userId: string) {
try {
await client.connect()
const result = await client.queryObject`
SELECT * FROM users WHERE id = ${userId}
`
return result.rows[0]
} finally {
await client.end()
}
}
serve(async (req) => {
const url = new URL(req.url)
const userId = url.searchParams.get("userId")
if (!userId) {
return new Response("userId is required", { status: 400 })
}
try {
const userData = await getUserData(userId)
return new Response(JSON.stringify(userData), {
headers: { "content-type": "application/json" }
})
} catch (error) {
return new Response(error.message, { status: 500 })
}
})
File Handling and Static Assets
Serve static files efficiently:
import { serveFile } from "https://deno.land/std/http/file_server.ts"
async function handleStaticFiles(req: Request): Promise<Response> {
const url = new URL(req.url)
const filepath = decodeURIComponent(url.pathname)
try {
return await serveFile(req, `./static${filepath}`)
} catch {
return new Response("File not found", { status: 404 })
}
}
serve(async (req) => {
const url = new URL(req.url)
if (url.pathname.startsWith("/static")) {
return handleStaticFiles(req)
}
return new Response("API endpoint")
})
API Routes and Middleware
Implement a structured API with middleware:
type Handler = (req: Request) => Promise<Response>
type Middleware = (handler: Handler) => Handler
const cors: Middleware = (handler) => async (req) => {
const response = await handler(req)
response.headers.set("Access-Control-Allow-Origin", "*")
return response
}
const logging: Middleware = (handler) => async (req) => {
const start = Date.now()
const response = await handler(req)
const duration = Date.now() - start
console.log(`${req.method} ${req.url} - ${duration}ms`)
return response
}
const router = new Map<string, Handler>()
router.set("/api/users", async (req) => {
if (req.method === "GET") {
return new Response(JSON.stringify([
{ id: 1, name: "John" },
{ id: 2, name: "Jane" }
]), {
headers: { "content-type": "application/json" }
})
}
return new Response("Method not allowed", { status: 405 })
})
const app = cors(logging(async (req) => {
const { pathname } = new URL(req.url)
const handler = router.get(pathname)
return handler ? handler(req) : new Response("Not found", { status: 404 })
}))
serve(app)
WebSocket Support
Implement real-time features with WebSockets:
import { serve } from "https://deno.land/std/http/server.ts"
import {
WebSocketClient,
WebSocketServer,
} from "https://deno.land/x/websocket/mod.ts"
const wss = new WebSocketServer(8080)
const clients = new Set<WebSocketClient>()
wss.on("connection", (ws) => {
clients.add(ws)
ws.on("message", (message) => {
// Broadcast to all clients
for (const client of clients) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message)
}
}
})
ws.on("close", () => {
clients.delete(ws)
})
})
Caching Strategies
Implement efficient caching:
const cache = new Map<string, { data: any; expires: number }>()
async function getCachedData(key: string, ttl: number = 3600000) {
const now = Date.now()
const cached = cache.get(key)
if (cached && cached.expires > now) {
return cached.data
}
const data = await fetchFreshData(key)
cache.set(key, {
data,
expires: now + ttl
})
return data
}
async function handler(req: Request): Promise<Response> {
const { pathname } = new URL(req.url)
try {
const data = await getCachedData(pathname)
return new Response(JSON.stringify(data), {
headers: {
"content-type": "application/json",
"cache-control": "public, max-age=3600"
}
})
} catch (error) {
return new Response(error.message, { status: 500 })
}
}
Error Handling and Logging
Implement robust error handling:
interface ErrorResponse {
error: string
code: string
requestId: string
}
class APIError extends Error {
constructor(
public status: number,
public code: string,
message: string
) {
super(message)
}
}
async function errorHandler(
req: Request,
handler: Handler
): Promise<Response> {
const requestId = crypto.randomUUID()
try {
return await handler(req)
} catch (error) {
console.error(`[${requestId}] Error:`, error)
if (error instanceof APIError) {
const response: ErrorResponse = {
error: error.message,
code: error.code,
requestId
}
return new Response(JSON.stringify(response), {
status: error.status,
headers: { "content-type": "application/json" }
})
}
return new Response(JSON.stringify({
error: "Internal Server Error",
code: "INTERNAL_ERROR",
requestId
}), {
status: 500,
headers: { "content-type": "application/json" }
})
}
}
Deployment and CI/CD
Set up continuous deployment:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy to Deno Deploy
uses: denoland/deployctl@v1
with:
project: your-project
entrypoint: main.ts
root: .
env:
DENO_DEPLOY_TOKEN: ${{ secrets.DENO_DEPLOY_TOKEN }}
Performance Monitoring
Implement performance tracking:
class PerformanceMonitor {
private metrics: Map<string, number[]> = new Map()
track(name: string, duration: number) {
if (!this.metrics.has(name)) {
this.metrics.set(name, [])
}
this.metrics.get(name)!.push(duration)
}
async measure<T>(
name: string,
fn: () => Promise<T>
): Promise<T> {
const start = performance.now()
try {
return await fn()
} finally {
const duration = performance.now() - start
this.track(name, duration)
}
}
getStats(name: string) {
const metrics = this.metrics.get(name) || []
return {
avg: metrics.reduce((a, b) => a + b, 0) / metrics.length,
min: Math.min(...metrics),
max: Math.max(...metrics),
count: metrics.length
}
}
}
Conclusion
Deno Deploy offers a powerful platform for building modern web applications:
- Secure by default
- TypeScript support
- Edge computing capabilities
- WebSocket support
- Built-in tooling
Best practices to remember:
- Use TypeScript for better type safety
- Implement proper error handling
- Cache appropriately
- Monitor performance
- Set up CI/CD pipelines
- Follow security best practices
By following these patterns and best practices, you can build robust, scalable applications on Deno Deploy.