Deployment
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 Workertypescript
1// src/index.ts
2import { createAppWorker } from '@morojs/moro';
3
4const app = await 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// Export for Cloudflare Workers
23export default {
24 async fetch(request: Request, env: any, ctx: ExecutionContext) {
25 return app.getHandler()(request, env, ctx);
26 }
27};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 Configurationtypescript
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 Configurationtypescript
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 Storagetypescript
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 = await 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// Session management with KV
40app.post('/auth/login')
41 .body(z.object({
42 email: z.string().email(),
43 password: z.string()
44 }))
45 .handler(async ({ body, env }) => {
46 // Validate credentials
47 const user = await validateCredentials(body.email, body.password);
48 if (!user) {
49 return { error: 'Invalid credentials' };
50 }
51
52 // Create session
53 const sessionId = crypto.randomUUID();
54 const sessionData = {
55 userId: user.id,
56 email: user.email,
57 createdAt: new Date().toISOString()
58 };
59
60 // Store session in KV (expires in 24 hours)
61 await env.SESSIONS.put(`session:${sessionId}`, JSON.stringify(sessionData), {
62 expirationTtl: 86400
63 });
64
65 return {
66 success: true,
67 sessionId,
68 user: { id: user.id, email: user.email }
69 };
70 }));
71
72// Middleware to check sessions
73const requireAuth = async (context: any, next: any) => {
74 const sessionId = context.headers.authorization?.replace('Bearer ', '');
75
76 if (!sessionId) {
77 return context.status(401).json({ error: 'Authentication required' });
78 }
79
80 const sessionData = await context.env.SESSIONS.get(`session:${sessionId}`, 'json');
81
82 if (!sessionData) {
83 return context.status(401).json({ error: 'Invalid session' });
84 }
85
86 context.user = sessionData;
87 await next();
88};D1 Database Integration
D1 Configurationtypescript
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 Routestypescript
1interface Env {
2 DB: D1Database;
3 CACHE: KVNamespace;
4}
5
6const app = await 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
18app.post('/users')
19 .body(z.object({
20 email: z.string().email(),
21 name: z.string()
22 }))
23 .handler(async ({ body, env }) => {
24 const id = crypto.randomUUID();
25
26 const { success } = await env.DB.prepare(
27 'INSERT INTO users (id, email, name) VALUES (?, ?, ?)'
28 ).bind(id, body.email, body.name).run();
29
30 if (!success) {
31 throw new Error('Failed to create user');
32 }
33
34 // Invalidate cache
35 await env.CACHE.delete('users:list');
36
37 return { id, ...body, created_at: new Date().toISOString() };
38 }));
39
40// Batch operations
41app.post('/users/batch')
42 .body(z.object({
43 users: z.array(z.object({
44 email: z.string().email(),
45 name: z.string()
46 })).max(100) // Limit batch size
47 }))
48 .handler(async ({ body, env }) => {
49 const statements = body.users.map(user =>
50 env.DB.prepare('INSERT INTO users (id, email, name) VALUES (?, ?, ?)')
51 .bind(crypto.randomUUID(), user.email, user.name)
52 );
53
54 const results = await env.DB.batch(statements);
55
56 return {
57 success: true,
58 inserted: results.filter(r => r.success).length,
59 failed: results.filter(r => !r.success).length
60 };
61 }));Durable Objects Integration
Durable Object Implementationtypescript
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 Routestypescript
1// src/index.ts
2interface Env {
3 CHAT_ROOMS: DurableObjectNamespace;
4 CACHE: KVNamespace;
5}
6
7const app = await 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// WebSocket upgrade for chat
30app.get('/chat/:roomId/ws')
31 .params(z.object({
32 roomId: z.string()
33 }))
34 .handler(async ({ params, env, request }) => {
35 const roomId = env.CHAT_ROOMS.idFromName(params.roomId);
36 const roomObject = env.CHAT_ROOMS.get(roomId);
37
38 // Forward WebSocket upgrade to Durable Object
39 return roomObject.fetch(request);
40 }));
41
42// Counter example with Durable Objects
43app.get('/counter/:name')
44 .params(z.object({
45 name: z.string()
46 }))
47 .handler(async ({ params, env }) => {
48 const counterId = env.COUNTERS.idFromName(params.name);
49 const counter = env.COUNTERS.get(counterId);
50
51 const response = await counter.fetch(new Request('https://dummy/increment', {
52 method: 'POST'
53 }));
54
55 const result = await response.json();
56 return result;
57 }));
58
59export { ChatRoom } from './durable-objects/ChatRoom';
60export default app.getWorkerHandler();Deployment Process
Deploy with Wranglertypescript
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/CDtypescript
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