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

Next Steps