Cloudflare Workers Deployment
Deploy your MoroJS applications to Cloudflare Workers for global edge computing. Ultra-fast performance, unlimited scalability, and integrated KV storage.
Quick Start
MoroJS provides native Cloudflare Workers support with automatic request/response transformation and access to Workers APIs like KV, Durable Objects, and R2.
Basic Worker
typescript
1// src/index.ts
2import { createAppWorker } from '@morojs/moro';
3
4const app = createAppWorker();
5
6app.get('/', () => {
7 return { message: 'Hello from Cloudflare Workers!' };
8});
9
10app.post('/users', {
11 body: z.object({
12 name: z.string(),
13 email: z.string().email()
14 }),
15 handler: ({ body }) => {
16 return {
17 success: true,
18 user: body
19 };
20 }
21});
22
23// Export for Cloudflare Workers
24export default {
25 async fetch(request: Request, env: any, ctx: ExecutionContext) {
26 return app.getHandler()(request, env, ctx);
27 }
28};
Workers Benefits
- • Global edge deployment (200+ locations)
- • Zero cold start latency
- • Unlimited scalability
- • Built-in KV storage and Durable Objects
- • Integrated with Cloudflare's security stack
Project Setup
Wrangler Configuration
typescript
1# Install Wrangler CLI
2npm install -g wrangler
3
4# Login to Cloudflare
5wrangler login
6
7# Initialize project
8wrangler init my-moro-worker --type=typescript
9
10# wrangler.toml
11name = "my-moro-api"
12main = "src/index.ts"
13compatibility_date = "2023-12-01"
14
15[env.production]
16name = "my-moro-api-prod"
17vars = { ENVIRONMENT = "production" }
18
19[env.staging]
20name = "my-moro-api-staging"
21vars = { ENVIRONMENT = "staging" }
22
23# KV namespaces
24[[kv_namespaces]]
25binding = "CACHE"
26id = "your-kv-namespace-id"
27preview_id = "your-preview-kv-namespace-id"
28
29[[kv_namespaces]]
30binding = "SESSIONS"
31id = "your-sessions-namespace-id"
32preview_id = "your-preview-sessions-namespace-id"
33
34# Durable Objects
35[[durable_objects.bindings]]
36name = "CHAT_ROOMS"
37class_name = "ChatRoom"
38
39# R2 buckets
40[[r2_buckets]]
41binding = "UPLOADS"
42bucket_name = "my-app-uploads"
43
44# Environment variables
45[vars]
46API_VERSION = "1.0.0"
47
48# Secrets (use wrangler secret put)
49# JWT_SECRET = "..." (set via CLI)
50# DATABASE_URL = "..." (set via CLI)
Package.json Configuration
typescript
1{
2 "name": "my-moro-worker",
3 "version": "1.0.0",
4 "scripts": {
5 "dev": "wrangler dev",
6 "deploy": "wrangler deploy",
7 "deploy:staging": "wrangler deploy --env staging",
8 "deploy:prod": "wrangler deploy --env production",
9 "tail": "wrangler tail",
10 "kv:create": "wrangler kv:namespace create CACHE",
11 "secret:put": "wrangler secret put"
12 },
13 "dependencies": {
14 "@morojs/moro": "^1.0.0",
15 "zod": "^3.22.0"
16 },
17 "devDependencies": {
18 "@cloudflare/workers-types": "^4.0.0",
19 "wrangler": "^3.0.0",
20 "typescript": "^5.0.0"
21 }
22}
KV Storage Integration
Using KV Storage
typescript
1// src/index.ts
2import { createAppWorker } from '@morojs/moro';
3
4interface Env {
5 CACHE: KVNamespace;
6 SESSIONS: KVNamespace;
7 JWT_SECRET: string;
8}
9
10const app = createAppWorker<Env>();
11
12// KV-based caching
13app.get('/users/:id', {
14 params: z.object({
15 id: z.string().uuid()
16 }),
17 handler: async ({ params, env }) => {
18 const cacheKey = `user:${params.id}`;
19
20 // Try cache first
21 const cached = await env.CACHE.get(cacheKey, 'json');
22 if (cached) {
23 return cached;
24 }
25
26 // Fetch from database (D1 or external API)
27 const user = await fetchUserFromDatabase(params.id);
28
29 if (user) {
30 // Cache for 5 minutes
31 await env.CACHE.put(cacheKey, JSON.stringify(user), {
32 expirationTtl: 300
33 });
34 }
35
36 return user || { error: 'User not found' };
37 }
38});
39
40// Session management with KV
41app.post('/auth/login', {
42 body: z.object({
43 email: z.string().email(),
44 password: z.string()
45 }),
46 handler: async ({ body, env }) => {
47 // Validate credentials
48 const user = await validateCredentials(body.email, body.password);
49 if (!user) {
50 return { error: 'Invalid credentials' };
51 }
52
53 // Create session
54 const sessionId = crypto.randomUUID();
55 const sessionData = {
56 userId: user.id,
57 email: user.email,
58 createdAt: new Date().toISOString()
59 };
60
61 // Store session in KV (expires in 24 hours)
62 await env.SESSIONS.put(`session:${sessionId}`, JSON.stringify(sessionData), {
63 expirationTtl: 86400
64 });
65
66 return {
67 success: true,
68 sessionId,
69 user: { id: user.id, email: user.email }
70 };
71 }
72});
73
74// Middleware to check sessions
75const requireAuth = async (context: any, next: any) => {
76 const sessionId = context.headers.authorization?.replace('Bearer ', '');
77
78 if (!sessionId) {
79 return context.status(401).json({ error: 'Authentication required' });
80 }
81
82 const sessionData = await context.env.SESSIONS.get(`session:${sessionId}`, 'json');
83
84 if (!sessionData) {
85 return context.status(401).json({ error: 'Invalid session' });
86 }
87
88 context.user = sessionData;
89 await next();
90};
D1 Database Integration
D1 Configuration
typescript
1# wrangler.toml
2[[d1_databases]]
3binding = "DB"
4database_name = "my-app-db"
5database_id = "your-d1-database-id"
6
7# Create D1 database
8# wrangler d1 create my-app-db
9
10# Run migrations
11# wrangler d1 migrations apply my-app-db --local
12
13# migrations/0001_initial.sql
14CREATE TABLE users (
15 id TEXT PRIMARY KEY,
16 email TEXT UNIQUE NOT NULL,
17 name TEXT,
18 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
19);
20
21CREATE TABLE posts (
22 id TEXT PRIMARY KEY,
23 title TEXT NOT NULL,
24 content TEXT,
25 author_id TEXT REFERENCES users(id),
26 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
27);
28
29CREATE INDEX idx_posts_author ON posts(author_id);
Using D1 in Routes
typescript
1interface Env {
2 DB: D1Database;
3 CACHE: KVNamespace;
4}
5
6const app = createAppWorker<Env>();
7
8// D1 database operations
9app.get('/users', {
10 handler: async ({ env }) => {
11 const { results } = await env.DB.prepare(
12 'SELECT id, email, name, created_at FROM users ORDER BY created_at DESC LIMIT 20'
13 ).all();
14
15 return results;
16 }
17});
18
19app.post('/users', {
20 body: z.object({
21 email: z.string().email(),
22 name: z.string()
23 }),
24 handler: async ({ body, env }) => {
25 const id = crypto.randomUUID();
26
27 const { success } = await env.DB.prepare(
28 'INSERT INTO users (id, email, name) VALUES (?, ?, ?)'
29 ).bind(id, body.email, body.name).run();
30
31 if (!success) {
32 throw new Error('Failed to create user');
33 }
34
35 // Invalidate cache
36 await env.CACHE.delete('users:list');
37
38 return { id, ...body, created_at: new Date().toISOString() };
39 }
40});
41
42// Batch operations
43app.post('/users/batch', {
44 body: z.object({
45 users: z.array(z.object({
46 email: z.string().email(),
47 name: z.string()
48 })).max(100) // Limit batch size
49 }),
50 handler: async ({ body, env }) => {
51 const statements = body.users.map(user =>
52 env.DB.prepare('INSERT INTO users (id, email, name) VALUES (?, ?, ?)')
53 .bind(crypto.randomUUID(), user.email, user.name)
54 );
55
56 const results = await env.DB.batch(statements);
57
58 return {
59 success: true,
60 inserted: results.filter(r => r.success).length,
61 failed: results.filter(r => !r.success).length
62 };
63 }
64});
Durable Objects Integration
Durable Object Implementation
typescript
1// src/durable-objects/ChatRoom.ts
2export class ChatRoom {
3 private state: DurableObjectState;
4 private sessions: Map<string, WebSocket> = new Map();
5
6 constructor(state: DurableObjectState, env: Env) {
7 this.state = state;
8 }
9
10 async fetch(request: Request): Promise<Response> {
11 const url = new URL(request.url);
12
13 if (url.pathname === '/websocket') {
14 return this.handleWebSocket(request);
15 }
16
17 if (url.pathname === '/messages') {
18 return this.handleMessages(request);
19 }
20
21 return new Response('Not found', { status: 404 });
22 }
23
24 private async handleWebSocket(request: Request): Promise<Response> {
25 const upgradeHeader = request.headers.get('Upgrade');
26 if (upgradeHeader !== 'websocket') {
27 return new Response('Expected Upgrade: websocket', { status: 426 });
28 }
29
30 const webSocketPair = new WebSocketPair();
31 const [client, server] = Object.values(webSocketPair);
32
33 const sessionId = crypto.randomUUID();
34 this.sessions.set(sessionId, server);
35
36 server.accept();
37
38 server.addEventListener('message', async (event) => {
39 const message = JSON.parse(event.data as string);
40
41 // Broadcast to all sessions
42 this.broadcast({
43 type: 'message',
44 data: message,
45 timestamp: new Date().toISOString(),
46 sessionId
47 });
48
49 // Persist message
50 await this.saveMessage(message);
51 });
52
53 server.addEventListener('close', () => {
54 this.sessions.delete(sessionId);
55 });
56
57 return new Response(null, {
58 status: 101,
59 webSocket: client
60 });
61 }
62
63 private broadcast(message: any) {
64 const messageStr = JSON.stringify(message);
65 this.sessions.forEach((ws) => {
66 try {
67 ws.send(messageStr);
68 } catch (error) {
69 // Remove closed connections
70 this.sessions.forEach((value, key) => {
71 if (value === ws) {
72 this.sessions.delete(key);
73 }
74 });
75 }
76 });
77 }
78
79 private async saveMessage(message: any) {
80 const messages = await this.state.storage.get('messages') || [];
81 messages.push({
82 ...message,
83 timestamp: new Date().toISOString()
84 });
85
86 // Keep only last 100 messages
87 if (messages.length > 100) {
88 messages.splice(0, messages.length - 100);
89 }
90
91 await this.state.storage.put('messages', messages);
92 }
93}
Using Durable Objects in Routes
typescript
1// src/index.ts
2interface Env {
3 CHAT_ROOMS: DurableObjectNamespace;
4 CACHE: KVNamespace;
5}
6
7const app = createAppWorker<Env>();
8
9// Get or create chat room
10app.get('/chat/:roomId', {
11 params: z.object({
12 roomId: z.string()
13 }),
14 handler: async ({ params, env }) => {
15 // Get Durable Object instance
16 const roomId = env.CHAT_ROOMS.idFromName(params.roomId);
17 const roomObject = env.CHAT_ROOMS.get(roomId);
18
19 // Forward request to Durable Object
20 const response = await roomObject.fetch(new Request('https://dummy/messages'));
21 const messages = await response.json();
22
23 return {
24 roomId: params.roomId,
25 messages
26 };
27 }
28});
29
30// WebSocket upgrade for chat
31app.get('/chat/:roomId/ws', {
32 params: z.object({
33 roomId: z.string()
34 }),
35 handler: async ({ params, env, request }) => {
36 const roomId = env.CHAT_ROOMS.idFromName(params.roomId);
37 const roomObject = env.CHAT_ROOMS.get(roomId);
38
39 // Forward WebSocket upgrade to Durable Object
40 return roomObject.fetch(request);
41 }
42});
43
44// Counter example with Durable Objects
45app.get('/counter/:name', {
46 params: z.object({
47 name: z.string()
48 }),
49 handler: async ({ params, env }) => {
50 const counterId = env.COUNTERS.idFromName(params.name);
51 const counter = env.COUNTERS.get(counterId);
52
53 const response = await counter.fetch(new Request('https://dummy/increment', {
54 method: 'POST'
55 }));
56
57 const result = await response.json();
58 return result;
59 }
60});
61
62export { ChatRoom } from './durable-objects/ChatRoom';
63export default app.getWorkerHandler();
Deployment Process
Deploy with Wrangler
typescript
1# Development deployment
2wrangler dev
3
4# Deploy to staging
5wrangler deploy --env staging
6
7# Deploy to production
8wrangler deploy --env production
9
10# Set secrets
11wrangler secret put JWT_SECRET --env production
12wrangler secret put DATABASE_URL --env production
13
14# Create KV namespaces
15wrangler kv:namespace create "CACHE" --env production
16wrangler kv:namespace create "SESSIONS" --env production
17
18# View logs
19wrangler tail --env production
20
21# View analytics
22wrangler analytics --env production
23
24# Package.json scripts
25{
26 "scripts": {
27 "dev": "wrangler dev",
28 "deploy:staging": "wrangler deploy --env staging",
29 "deploy:prod": "wrangler deploy --env production",
30 "logs": "wrangler tail --env production",
31 "setup:kv": "npm run kv:create:cache && npm run kv:create:sessions",
32 "kv:create:cache": "wrangler kv:namespace create CACHE --env production",
33 "kv:create:sessions": "wrangler kv:namespace create SESSIONS --env production"
34 }
35}
GitHub Actions CI/CD
typescript
1# .github/workflows/deploy.yml
2name: Deploy to Cloudflare Workers
3
4on:
5 push:
6 branches: [main, develop]
7
8jobs:
9 deploy:
10 runs-on: ubuntu-latest
11
12 steps:
13 - uses: actions/checkout@v3
14
15 - name: Setup Node.js
16 uses: actions/setup-node@v3
17 with:
18 node-version: '18'
19 cache: 'npm'
20
21 - name: Install dependencies
22 run: npm ci
23
24 - name: Build
25 run: npm run build
26
27 - name: Deploy to staging
28 if: github.ref == 'refs/heads/develop'
29 env:
30 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
31 run: npx wrangler deploy --env staging
32
33 - name: Deploy to production
34 if: github.ref == 'refs/heads/main'
35 env:
36 CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
37 run: npx wrangler deploy --env production
38
39 - name: Run integration tests
40 env:
41 WORKER_URL: ${{ steps.deploy.outputs.url }}
42 run: npm run test:integration