Deno ile REST API Geliştirme ve Cloudflare Deploy
Bir süredir Node.js ile API yazıyorsanız ve “acaba daha hafif, daha güvenli bir şey var mı?” diye düşünüyorsanız, Deno tam olarak o ihtiyaca cevap veriyor. Ben de aynı noktadan başladım. Production’da çalışan Node.js servisleri, onlarca node_modules klasörü, npm audit uyarıları… Bir noktada Ryan Dahl’ın “Node.js hakkında pişman olduğum 10 şey” konuşmasını izleyince Deno’ya ciddi bir şans vermek istedim. Bu yazıda Deno ile sıfırdan REST API geliştirip Cloudflare’e deploy etmeyi, gerçek dünyada karşılaştığım sorunlarla birlikte aktaracağım.
Deno Nedir, Node.js’ten Farkı Ne?
Deno, aynı yaratıcıdan (Ryan Dahl) çıkmış ama Node.js’in yanlışlarını düzeltmek amacıyla tasarlanmış bir JavaScript/TypeScript çalışma ortamı. En temel farklar şunlar:
- TypeScript desteği native olarak geliyor, ayrıca
tscçalıştırmanıza gerek yok node_modulesklasörü yok, modüller URL üzerinden import ediliyor- Güvenlik önce geliyor: dosya sistemi, ağ, environment variable erişimi için explicit izin vermeniz gerekiyor
- Web standartlarına uygun API’lar:
fetch,Request,Responsegibi objeler browser’dakiyle aynı - Deno Deploy ile edge’e deploy çok kolay
Bu son madde bu yazı için önemli. Cloudflare Workers ve Deno Deploy, aynı Web API standartlarını kullandığı için yazdığınız kod neredeyse sıfır değişiklikle her iki platformda çalışıyor.
Geliştirme Ortamını Kurma
Önce Deno’yu kuralım:
# Linux/macOS
curl -fsSL https://deno.land/install.sh | sh
# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex
# Homebrew ile
brew install deno
# Kurulumu doğrula
deno --version
VSCode kullanıyorsanız Deno extension’ını yükleyin ve projenizin root’una şu dosyayı ekleyin:
mkdir my-api && cd my-api
# .vscode/settings.json oluştur
mkdir .vscode
cat > .vscode/settings.json << 'EOF'
{
"deno.enable": true,
"deno.lint": true,
"deno.unstable": false
}
EOF
Bu adımı atlarsanız editör sürekli “Cannot find module” uyarısı verecek ve sinirinizi bozacak. Bizzat yaşadım.
İlk REST API: Hono Framework ile
Deno’nun standart kütüphanesi ile de API yazılabilir ama ben production projelerinde Hono framework’ünü tercih ediyorum. Hem Deno hem Cloudflare Workers hem de Node.js üzerinde çalışıyor, bundle size son derece küçük ve middleware ekosistemi yeterince olgun.
// main.ts
import { Hono } from "npm:hono@4";
import { cors } from "npm:hono/cors";
import { logger } from "npm:hono/logger";
const app = new Hono();
// Global middleware
app.use("*", logger());
app.use(
"/api/*",
cors({
origin: ["https://yourdomain.com", "http://localhost:3000"],
allowMethods: ["GET", "POST", "PUT", "DELETE"],
})
);
// Health check
app.get("/health", (c) => {
return c.json({
status: "ok",
timestamp: new Date().toISOString(),
runtime: "deno",
});
});
// Temel CRUD route'ları
app.get("/api/products", async (c) => {
// Gerçekte DB'den çekersiniz
const products = [
{ id: 1, name: "Laptop", price: 25000 },
{ id: 2, name: "Mouse", price: 350 },
];
return c.json({ data: products, total: products.length });
});
Deno.serve({ port: 8000 }, app.fetch);
Çalıştırmak için:
deno run --allow-net --allow-read --allow-env main.ts
--allow-net olmadan hiçbir ağ isteği yapamıyorsunuz. İlk başta bu biraz rahatsız edici geliyor, ama bir serviste yanlışlıkla açık bırakılan bir güvenlik açığının dışarı çıkmaması için bu kısıtlama aslında çok değerli. Production’da minimum yetki prensibinin framework tarafından zorlanması güzel bir şey.
Gerçek Dünya Senaryosu: Ürün Kataloğu API’si
Basit bir e-ticaret ürün kataloğu örneği üzerinden devam edelim. Deno KV (built-in key-value store) kullanacağız, dış bir veritabanına ihtiyaç duymayacağız:
// routes/products.ts
import { Hono } from "npm:hono@4";
import { zValidator } from "npm:@hono/zod-validator";
import { z } from "npm:zod@3";
const productRoute = new Hono();
// Şema tanımı
const ProductSchema = z.object({
name: z.string().min(2).max(100),
price: z.number().positive(),
stock: z.number().int().min(0),
category: z.string(),
});
// Deno KV bağlantısı
const kv = await Deno.openKv();
// Tüm ürünleri listele
productRoute.get("/", async (c) => {
const page = Number(c.req.query("page") || 1);
const limit = Number(c.req.query("limit") || 10);
const products = [];
const iter = kv.list<Product>({ prefix: ["products"] });
for await (const entry of iter) {
products.push({ id: entry.key[1], ...entry.value });
}
const start = (page - 1) * limit;
const paginated = products.slice(start, start + limit);
return c.json({
data: paginated,
pagination: {
page,
limit,
total: products.length,
totalPages: Math.ceil(products.length / limit),
},
});
});
// Ürün oluştur
productRoute.post(
"/",
zValidator("json", ProductSchema),
async (c) => {
const body = c.req.valid("json");
const id = crypto.randomUUID();
await kv.set(["products", id], {
...body,
createdAt: new Date().toISOString(),
});
return c.json({ id, ...body }, 201);
}
);
// Ürün güncelle
productRoute.put("/:id", zValidator("json", ProductSchema.partial()), async (c) => {
const id = c.req.param("id");
const existing = await kv.get<Product>(["products", id]);
if (!existing.value) {
return c.json({ error: "Ürün bulunamadı" }, 404);
}
const updates = await c.req.valid("json");
const updated = { ...existing.value, ...updates, updatedAt: new Date().toISOString() };
await kv.set(["products", id], updated);
return c.json({ id, ...updated });
});
export { productRoute };
Zod ile validation entegrasyonu çok temiz. Node.js’te ayrıca express-validator veya joi kurmanız gerekirdi, burada npm:zod ile direkt import ediyorsunuz.
Middleware ve Authentication
JWT tabanlı authentication middleware’i şöyle yazabilirsiniz:
// middleware/auth.ts
import { createMiddleware } from "npm:hono/factory";
import { verify } from "npm:hono/jwt";
const JWT_SECRET = Deno.env.get("JWT_SECRET") || "dev-secret-change-in-prod";
export const authMiddleware = createMiddleware(async (c, next) => {
const authHeader = c.req.header("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return c.json({ error: "Yetkisiz erişim" }, 401);
}
const token = authHeader.replace("Bearer ", "");
try {
const payload = await verify(token, JWT_SECRET);
c.set("userId", payload.sub);
c.set("userRole", payload.role);
await next();
} catch (_e) {
return c.json({ error: "Geçersiz veya süresi dolmuş token" }, 401);
}
});
// Login endpoint için token üretimi
export async function generateToken(userId: string, role: string): Promise<string> {
const { sign } = await import("npm:hono/jwt");
return sign(
{
sub: userId,
role,
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24, // 24 saat
},
JWT_SECRET
);
}
Ana dosyada route’ları birleştirin:
// main.ts - güncellenmiş versiyon
import { Hono } from "npm:hono@4";
import { cors } from "npm:hono/cors";
import { logger } from "npm:hono/logger";
import { productRoute } from "./routes/products.ts";
import { authMiddleware } from "./middleware/auth.ts";
const app = new Hono();
app.use("*", logger());
app.use("/api/*", cors());
// Public route'lar
app.get("/health", (c) => c.json({ status: "ok" }));
app.post("/api/auth/login", async (c) => {
const { username, password } = await c.req.json();
// Gerçekte DB kontrolü yaparsınız
if (username === "admin" && password === "secret") {
const { generateToken } = await import("./middleware/auth.ts");
const token = await generateToken("user-1", "admin");
return c.json({ token });
}
return c.json({ error: "Hatalı kullanıcı adı veya şifre" }, 401);
});
// Korumalı route'lar
app.use("/api/products/*", authMiddleware);
app.route("/api/products", productRoute);
Deno.serve({ port: 8000 }, app.fetch);
Cloudflare Workers’a Deploy
Şimdi asıl konuya gelelim. Cloudflare Workers, Deno ile aynı Web API standartlarını kullandığı için kodunuzu minimum değişiklikle deploy edebiliyorsunuz.
Önce Wrangler CLI’yi kurun:
npm install -g wrangler
# Cloudflare hesabınıza login olun
wrangler login
# Proje dizininde wrangler.toml oluşturun
cat > wrangler.toml << 'EOF'
name = "my-product-api"
main = "src/worker.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]
[vars]
ENVIRONMENT = "production"
[[kv_namespaces]]
binding = "PRODUCTS_KV"
id = "xxxx-kv-namespace-id-xxxx"
[secrets]
JWT_SECRET = "your-secret-here"
EOF
Cloudflare Workers için kodu biraz adapte etmemiz gerekiyor. Deno.serve yerine export default kullanacağız:
// src/worker.ts - Cloudflare Workers versiyonu
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
// Cloudflare Workers'da environment binding'leri farklı gelir
type Env = {
PRODUCTS_KV: KVNamespace;
JWT_SECRET: string;
ENVIRONMENT: string;
};
const app = new Hono<{ Bindings: Env }>();
app.use("*", logger());
app.use("/api/*", cors());
app.get("/health", (c) => {
return c.json({
status: "ok",
environment: c.env.ENVIRONMENT,
runtime: "cloudflare-workers",
});
});
app.get("/api/products", async (c) => {
// Deno KV yerine Cloudflare KV kullanıyoruz
const list = await c.env.PRODUCTS_KV.list({ prefix: "product:" });
const products = await Promise.all(
list.keys.map(async (key) => {
const value = await c.env.PRODUCTS_KV.get(key.name, "json");
return value;
})
);
return c.json({ data: products.filter(Boolean) });
});
app.post("/api/products", async (c) => {
const body = await c.req.json();
const id = crypto.randomUUID();
await c.env.PRODUCTS_KV.put(
`product:${id}`,
JSON.stringify({ id, ...body, createdAt: new Date().toISOString() })
);
return c.json({ id, ...body }, 201);
});
// Cloudflare Workers export formatı
export default app;
Deploy etmek için:
# KV namespace oluştur
wrangler kv:namespace create "PRODUCTS_KV"
# Çıktıdaki ID'yi wrangler.toml'a ekleyin
# Secret ekle
wrangler secret put JWT_SECRET
# Deploy et
wrangler deploy
# Logları takip et
wrangler tail
Deno Deploy Üzerinden Alternatif
Cloudflare yerine Deno Deploy kullanmak isteyenler için süreç çok daha basit. GitHub entegrasyonu ile her push’ta otomatik deploy oluyor:
# deployctl CLI kur
deno install -A jsr:@deno/deployctl
# Deploy et (GitHub repo bağlıysa otomatik, manuel deploy için)
deployctl deploy --project=my-product-api main.ts
# Environment variable ekle (Deno Deploy dashboard'dan da yapılabilir)
deployctl env set JWT_SECRET=your-secret-value --project=my-product-api
deno.json dosyanıza task tanımlarsanız geliştirme süreci çok daha akıcı hale gelir:
{
"tasks": {
"dev": "deno run --watch --allow-net --allow-read --allow-env --allow-write main.ts",
"test": "deno test --allow-net --allow-read --allow-env",
"deploy": "deployctl deploy --project=my-product-api main.ts",
"lint": "deno lint",
"fmt": "deno fmt"
},
"imports": {
"hono": "npm:hono@4",
"hono/cors": "npm:hono/cors",
"hono/logger": "npm:hono/logger",
"hono/jwt": "npm:hono/jwt",
"zod": "npm:zod@3"
}
}
Artık deno task dev ile geliştirme sunucusunu ayağa kaldırabilirsiniz. Import map sayesinde kod içinde from "hono" diye import ediyorsunuz, tam URL yazmak zorunda kalmıyorsunuz.
Performans ve Monitoring
Edge deploy sonrası monitoring için Cloudflare’in built-in analytics yeterli oluyor çoğu zaman, ama özel metrikler için şöyle bir middleware ekleyebilirsiniz:
// middleware/metrics.ts
import { createMiddleware } from "hono/factory";
export const metricsMiddleware = createMiddleware(async (c, next) => {
const start = Date.now();
const method = c.req.method;
const path = c.req.path;
await next();
const duration = Date.now() - start;
const status = c.res.status;
// Cloudflare Analytics Engine veya başka bir sink'e yazın
console.log(
JSON.stringify({
type: "request",
method,
path,
status,
duration,
timestamp: new Date().toISOString(),
cf: c.req.raw.cf
? {
country: (c.req.raw.cf as Record<string, unknown>).country,
datacenter: (c.req.raw.cf as Record<string, unknown>).colo,
}
: undefined,
})
);
});
c.req.raw.cf objesi Cloudflare’e özgü metadata içeriyor. Hangi ülkeden geldiği, hangi data center’dan serve edildiği gibi bilgilere burada ulaşabiliyorsunuz. Bu bilgileri rate limiting veya geo-based routing için kullanabilirsiniz.
Yerel Geliştirme ile Production Pariteleri
En sık karşılaştığım sorun şu: yerel ortamda her şey çalışıyor, Cloudflare’e çıkınca patlar. Bunun önüne geçmek için wrangler dev kullanın, gerçek Cloudflare ortamını simüle ediyor:
# Cloudflare Workers'ı yerel simüle et
wrangler dev --local
# Gerçek Cloudflare edge'ini kullan (daha yavaş ama daha doğru)
wrangler dev --remote
# Belirli bir port'ta çalıştır
wrangler dev --port 8787
--local flag’i ile KV, D1 gibi servisleri yerel simüle ediyor. --remote kullandığınızda gerçek Cloudflare altyapısına bağlanıyor, bu biraz daha maliyetli ama edge davranışını birebir test edebiliyorsunuz.
Dikkat Edilmesi Gereken Noktalar
Deno ve Cloudflare Workers’ta geliştirirken birkaç şeyi baştan bilmek çok zaman kazandırır:
- Cold start süreleri: Deno Deploy ve Cloudflare Workers ikisi de V8 Isolate kullandığı için cold start neredeyse sıfır. Lambda ile kıyasladığınızda fark çok belirgin.
- Execution time limitleri: Cloudflare Workers free plan’da 10ms CPU süresi var. Yoğun hesaplama yapan işleri Workers’a taşımayın, bu iş için Cloudflare Queues ve bir backend kombinasyonu daha sağlıklı.
- Global state yok: Her request izole bir context’te çalışıyor. Module scope’ta bir şey tanımladığınızda o değer requestler arasında paylaşılmıyor. In-memory cache için Cloudflare Cache API veya KV kullanın.
npm:prefix’i: Deno 2.x ile npm paketleri çok daha stabil çalışıyor ama bazı native addon’lar hala çalışmıyor. Özelliklebcryptgibi native binding’li paketler yerine pure JS alternatifleri tercih edin.- File system yok: Cloudflare Workers’da dosya okuma/yazma yapamıyorsunuz. Statik dosyalar için Cloudflare Pages, dinamik storage için R2 kullanın.
Sonuç
Deno ile REST API geliştirip Cloudflare’e deploy etmek, başta karmaşık görünse de birkaç kez yaptıktan sonra Node.js + Express kombinasyonundan çok daha temiz hissettiriyor. TypeScript native, güvenlik model sağlıklı, deploy süreci hızlı.
Benim tercihim özellikle küçük ve orta ölçekli API’lar için bu stack: Hono + Deno KV (veya Cloudflare KV) + Cloudflare Workers. Aylık 100.000 request’e kadar ücretsiz, global edge network ile latency minimumlarda, maintenance yükü sıfıra yakın.
Eğer zaten Cloudflare altyapısı kullanıyorsanız ve yeni bir servis ayağa kaldırmanız gerekiyorsa, bu stack’i denemeden geçmeyin. İlk 15 dakikayı kuruluşa harcarsınız, geri kalanını gerçek kod yazarak geçirirsiniz. Ve node_modules klasörüne bir daha bakmak zorunda kalmazsınız, ki bu başlı başına büyük bir kazanım.
