Skip to main content

Code with Workers

Overview

In this part, you'll learn the fundamentals of Cloudflare Workers and create your first "Hello World" Worker. Since you've already completed the setup in prerequisites, we'll dive straight into understanding Workers and building your first application.

What are Cloudflare Workers?

Cloudflare Workers is a serverless computing platform that runs JavaScript/TypeScript code at Cloudflare's edge locations worldwide. They allow you to:

  • Execute code at the edge (closest to your users)
  • Handle HTTP requests and responses
  • Integrate with Cloudflare services (R2, D1, KV, etc.)
  • Scale automatically without managing servers

Workers Runtime Environment

Workers operate in a unique runtime environment with access to bindings:

export interface Env {
// Environment bindings are injected here
CLOUDFLARE_API_TOKEN: string; // Secret binding
MY_KV: KVNamespace; // KV binding
MY_R2: R2Bucket; // R2 binding
}

export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
// Your Worker logic here
// env contains all your bindings
// ctx provides execution context methods
return new Response("Hello World!");
},
};

TLDR;

🔒 🚀 Let me go whoooooosh

This content is password-protected. Enter the password to view the content.


Lab 1.1: Foundation - Hello World API

Lab Objective: Create and deploy your first Cloudflare Worker with a basic API endpoint to understand the development workflow and Worker architecture.

What You'll Learn: Project structure, local development, edge deployment, and basic request handling.

What You'll Build: A simple Worker with a single / endpoint that returns "Hello World!" - this will be the foundation for our progressive API.

Step 1: Create a New Worker Project

Since you already have wrangler installed and authenticated from Module 0, let's create a new Worker:

npm create cloudflare@latest hello-workers-<YOUR_NAME>

When prompted:

  • Choose Hello World Example
  • Which template: Worker only
  • Use TypeScript: Yes
  • Use git: Yes (recommended)
  • Deploy now: No (we'll deploy manually) worker-template-selection

Step 2: Explore the Project Structure

Navigate to your project and examine the key files:

cd hello-workers-<YOUR_NAME>
ls -la

Your project contains these essential files:

  • wrangler.jsonc - Worker configuration
  • src/index.ts - Your Worker code
  • package.json - Project dependencies
  • .gitignore - Git ignore rules

Step 3: Create Your Foundation API

Replace the default code with our foundation API structure:

src/index.ts
export interface Env {
// Environment bindings will be added in later labs
}

export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;

// Foundation endpoint - we'll add more in Lab 1.2
if (path === "/") {
return new Response(
JSON.stringify({
message: "Hello World!",
timestamp: new Date().toISOString(),
version: "1.0",
}),
{
headers: { "Content-Type": "application/json" },
}
);
}

// Default 404 response
return new Response(
JSON.stringify({
error: "Not Found",
path: path,
}),
{
status: 404,
headers: { "Content-Type": "application/json" },
}
);
},
};

Code Breakdown:

  • export interface Env - TypeScript interface for environment bindings (we'll expand this)
  • export default - Exports the Worker handler
  • fetch - The main function that handles HTTP requests
  • new URL(request.url) - Parses the request URL for routing
  • JSON.stringify() - Returns structured JSON responses
  • Content-Type: application/json - Sets proper response headers

Step 4: Develop Locally

Start the development server:

npx wrangler dev

What happens:

  • Wrangler starts a local development server
  • Your Worker runs on http://localhost:8787
  • Changes to your code are automatically reloaded

Test your Foundation API:

  1. Open your browser to http://localhost:8787
  2. You should see a JSON response with "Hello World!" message
  3. Try http://localhost:8787/nonexistent to see the 404 response

Step 5: Deploy to the Edge

Deploy your Worker to Cloudflare's global network:

npx wrangler deploy

What happens:

  • Your code is uploaded to Cloudflare
  • A unique URL is generated (e.g., https://hello-workers-<YOUR_NAME>.your-subdomain.workers.dev)
  • Your Worker is now running on 200+ locations worldwide

Test the deployed Worker:

  1. Copy the URL from the deployment output
  2. Visit it in your browser
  3. You should see the JSON response served from the edge

Lab 1.1 Summary

What you've built: A foundation API Worker with:

  • Single / endpoint returning JSON
  • Basic error handling (404 responses)
  • Proper JSON headers
  • TypeScript structure ready for expansion

🎯 Next: In Lab 1.2, we'll build upon this foundation by adding more endpoints and routing to the same Worker code.


Lab 1.2: Enhanced Routing - Multiple API Endpoints

Lab Objective: Build upon your Lab 1.1 foundation Worker by adding multiple API endpoints and enhanced routing.

Prerequisites: Completed Lab 1.1 with the foundation API Worker deployed.

What You'll Learn: Advanced routing, request information handling, health checks, and structured API responses.

What You'll Build: Expand your existing Worker with /info, /health, and /time endpoints while maintaining the foundation / endpoint.

Step 1: Enhance Your Existing Worker

Now let's build upon the foundation code from Lab 1.1. Open your existing hello-workers-<YOUR_NAME> project and enhance the src/index.ts file:

src/index.ts
export interface Env {
// Environment bindings will be added in later labs
}

export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;

// Enhanced routing - building upon Lab 1.1
switch (path) {
case "/":
// Enhanced foundation endpoint from Lab 1.1
return new Response(
JSON.stringify({
message: "Hello World!",
timestamp: new Date().toISOString(),
version: "1.1", // Updated version
availableEndpoints: [
"GET /",
"GET /info",
"GET /health",
"GET /time",
],
}),
{
headers: { "Content-Type": "application/json" },
}
);

case "/info":
// Request information endpoint
const info = {
method: method,
url: request.url,
path: path,
userAgent: request.headers.get("user-agent"),
cfRay: request.headers.get("cf-ray"),
cfCountry: request.headers.get("cf-ipcountry"),
timestamp: new Date().toISOString(),
};
return new Response(JSON.stringify(info, null, 2), {
headers: { "Content-Type": "application/json" },
});

case "/health":
// Health check endpoint
return new Response(
JSON.stringify({
status: "healthy",
uptime: "operational",
timestamp: new Date().toISOString(),
service: "Cloudflare Worker API",
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);

case "/time":
// Time information endpoint
const now = new Date();
return new Response(
JSON.stringify({
timestamp: now.toISOString(),
unix: Math.floor(now.getTime() / 1000),
timezone: "UTC",
formatted: now.toUTCString(),
}),
{
headers: { "Content-Type": "application/json" },
}
);

default:
// Enhanced 404 response
return new Response(
JSON.stringify({
error: "Not Found",
path: path,
method: method,
availableEndpoints: [
"GET /",
"GET /info",
"GET /health",
"GET /time",
],
}),
{
status: 404,
headers: { "Content-Type": "application/json" },
}
);
}
},
};

Step 2: Test Your Enhanced API Locally

Start your development server (if not already running):

npx wrangler dev

Test all your new endpoints:

  1. GET http://localhost:8787/ - Enhanced foundation endpoint with available endpoints list
  2. GET http://localhost:8787/info - Request information including Cloudflare headers
  3. GET http://localhost:8787/health - Health check with service status
  4. GET http://localhost:8787/time - Current time in multiple formats
  5. GET http://localhost:8787/invalid - Enhanced 404 response with available endpoints

Step 3: Deploy Your Enhanced API

Deploy your updated Worker:

npx wrangler deploy

Test your deployed endpoints using the same URLs but with your Worker's domain.

Step 4: Understanding the Enhancements

What's new from Lab 1.1:

  • Enhanced routing: Switch statement for cleaner path handling
  • Request information: Access to Cloudflare-specific headers (cf-ray, cf-ipcountry)
  • Structured responses: Consistent JSON format across all endpoints
  • Better error handling: Informative 404 responses with available endpoints
  • API discovery: Root endpoint lists all available endpoints

Lab 1.2 Summary

What you've built: Enhanced your foundation API with:

  • 4 functional endpoints (/, /info, /health, /time)
  • Cloudflare-specific request information
  • Consistent JSON responses
  • Enhanced error handling
  • API endpoint discovery

🎯 Next: In Lab 1.3, we'll add environment variables and secrets to make our API configurable and secure.


Lab 1.3: Configuration & Secrets - Environment Management

Lab Objective: Build upon your Lab 1.2 API by adding environment variables, secrets management, and configuration endpoints.

Prerequisites: Completed Lab 1.2 with the enhanced API Worker deployed.

What You'll Learn: Environment variables, secrets management, TypeScript bindings, and secure configuration practices.

What You'll Build: Add /config and /secrets endpoints to your existing API while demonstrating secure environment management.

Step 1: Understanding Environment Variables vs Secrets

Environment Variables: Non-sensitive configuration values that can vary between environments. Secrets: Sensitive data like API keys, tokens, passwords that need encryption.

Step 2: Configure Environment Variables

First, let's add environment variables to your wrangler.jsonc file. In your existing hello-workers-<YOUR_NAME> project, update the configuration:

wrangler.jsonc
{
"name": "hello-workers-<YOUR_NAME>",
"main": "src/index.ts",
"compatibility_date": "2024-01-01",
// Environment variables (non-sensitive)
"vars": {
"API_VERSION": "1.2",
"ENVIRONMENT": "development",
"API_NAME": "BlazeHack API",
"MAX_REQUESTS_PER_MINUTE": "100"
}
}

Step 3: Add Secrets

Add secrets using the Wrangler CLI (these are encrypted and secure):

# Add secrets to your Worker
npx wrangler secret put API_SECRET_KEY
# When prompted, enter: blazehack-secret-2025

npx wrangler secret put ADMIN_TOKEN
# When prompted, enter: admin-token-xyz789

Step 4: Update TypeScript Interface

Update your TypeScript interface to include the new environment bindings:

src/index.ts
export interface Env {
// Environment variables (from wrangler.jsonc)
API_VERSION: string;
ENVIRONMENT: string;
API_NAME: string;
MAX_REQUESTS_PER_MINUTE: string;

// Secrets (from wrangler secret)
API_SECRET_KEY: string;
ADMIN_TOKEN: string;
}

Step 5: Enhance Your Worker with Configuration Endpoints

Now let's build upon your Lab 1.2 code by adding the new configuration endpoints. Update your src/index.ts file:

src/index.ts
export interface Env {
// Environment variables (from wrangler.jsonc)
API_VERSION: string;
ENVIRONMENT: string;
API_NAME: string;
MAX_REQUESTS_PER_MINUTE: string;

// Secrets (from wrangler secret)
API_SECRET_KEY: string;
ADMIN_TOKEN: string;
}

export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;

// Enhanced routing - building upon Lab 1.2
switch (path) {
case "/":
// Enhanced foundation endpoint with environment info
return new Response(
JSON.stringify({
message: "Hello World!",
api: env.API_NAME || "BlazeHack API",
version: env.API_VERSION || "1.2",
environment: env.ENVIRONMENT || "development",
timestamp: new Date().toISOString(),
availableEndpoints: [
"GET /",
"GET /info",
"GET /health",
"GET /time",
"GET /config",
"GET /secrets",
],
}),
{
headers: { "Content-Type": "application/json" },
}
);

case "/config":
// NEW: Configuration endpoint - shows non-sensitive environment variables
return new Response(
JSON.stringify({
apiName: env.API_NAME,
apiVersion: env.API_VERSION,
environment: env.ENVIRONMENT,
maxRequestsPerMinute: env.MAX_REQUESTS_PER_MINUTE,
timestamp: new Date().toISOString(),
note: "This endpoint shows non-sensitive configuration only",
}),
{
headers: { "Content-Type": "application/json" },
}
);

case "/secrets":
// NEW: Secrets demonstration endpoint
const hasSecretKey = !!env.API_SECRET_KEY;
const hasAdminToken = !!env.ADMIN_TOKEN;

return new Response(
JSON.stringify({
secretsAvailable: {
apiSecretKey: hasSecretKey,
adminToken: hasAdminToken,
},
secretKeyLength: env.API_SECRET_KEY ? env.API_SECRET_KEY.length : 0,
secretKeyPreview: env.API_SECRET_KEY
? env.API_SECRET_KEY.substring(0, 4) + "****"
: "not set",
timestamp: new Date().toISOString(),
note: "Secrets are encrypted and never fully exposed in responses",
}),
{
headers: { "Content-Type": "application/json" },
}
);

// Keep existing endpoints from Lab 1.2
case "/info":
case "/health":
case "/time":
// [Previous Lab 1.2 code continues here - abbreviated for space]

default:
return new Response(
JSON.stringify({
error: "Not Found",
availableEndpoints: [
"/",
"/info",
"/health",
"/time",
"/config",
"/secrets",
],
}),
{ status: 404, headers: { "Content-Type": "application/json" } }
);
}
},
};

Step 6: Test Your Enhanced Configuration API

Test locally with your environment variables:

npx wrangler dev

Test your new endpoints:

  1. GET http://localhost:8787/ - Enhanced foundation with environment info
  2. GET http://localhost:8787/config - Shows non-sensitive configuration
  3. GET http://localhost:8787/secrets - Demonstrates secure secret handling
  4. All previous endpoints from Lab 1.2 still work

Step 7: Deploy with Environment Configuration

Deploy your enhanced Worker:

npx wrangler deploy

The environment variables from wrangler.jsonc and secrets will be automatically included.

Lab 1.3 Summary

What you've built: Enhanced your API with configuration management:

  • Environment variables in wrangler.jsonc
  • Encrypted secrets via Wrangler CLI
  • /config endpoint showing non-sensitive configuration
  • /secrets endpoint demonstrating secure secret access
  • TypeScript interfaces for type safety
  • 6 total endpoints building upon previous labs

🎯 Next: In Lab 1.4, we'll add security features, CORS, and testing to complete our production-ready API.


Lab 1.4: Production Ready - Security & Testing

Lab Objective: Complete your progressive API by adding security features, CORS configuration, and comprehensive testing.

Prerequisites: Completed Lab 1.3 with configuration and secrets management.

What You'll Learn: CORS handling, input validation, error handling, authentication concepts, and testing strategies.

What You'll Build: Transform your Lab 1.3 API into a production-ready, secure API with proper error handling and testing.

Step 1: Add CORS and Security Headers

Let's enhance your Lab 1.3 API with security features. Update your src/index.ts file to include CORS and security:

src/index.ts
export interface Env {
// Environment variables (from wrangler.jsonc)
API_VERSION: string;
ENVIRONMENT: string;
API_NAME: string;
MAX_REQUESTS_PER_MINUTE: string;

// Secrets (from wrangler secret)
API_SECRET_KEY: string;
ADMIN_TOKEN: string;
}

// Helper function for CORS headers
function corsHeaders(origin?: string) {
return {
"Access-Control-Allow-Origin": origin || "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400",
};
}

// Helper function for security headers
function securityHeaders() {
return {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "strict-origin-when-cross-origin",
};
}

export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
const method = request.method;
const origin = request.headers.get("Origin");

// Handle CORS preflight requests
if (method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
...corsHeaders(origin),
...securityHeaders(),
},
});
}

// Production-ready routing with error handling
try {
let response: Response;

switch (path) {
case "/":
// Enhanced foundation endpoint (from all previous labs)
response = new Response(
JSON.stringify({
message: "Hello World!",
api: env.API_NAME || "BlazeHack API",
version: env.API_VERSION || "1.3",
environment: env.ENVIRONMENT || "development",
timestamp: new Date().toISOString(),
availableEndpoints: [
"GET /",
"GET /info",
"GET /health",
"GET /time",
"GET /config",
"GET /secrets",
],
security: "CORS enabled, security headers applied",
}),
{
headers: { "Content-Type": "application/json" },
}
);
break;

case "/info":
// Request information endpoint (from Lab 1.2)
const info = {
method: method,
url: request.url,
path: path,
userAgent: request.headers.get("user-agent"),
cfRay: request.headers.get("cf-ray"),
cfCountry: request.headers.get("cf-ipcountry"),
timestamp: new Date().toISOString(),
security: "Headers validated and sanitized",
};
response = new Response(JSON.stringify(info, null, 2), {
headers: { "Content-Type": "application/json" },
});
break;

case "/health":
// Health check endpoint (from Lab 1.2)
response = new Response(
JSON.stringify({
status: "healthy",
uptime: "operational",
environment: env.ENVIRONMENT || "unknown",
version: env.API_VERSION || "1.0",
timestamp: new Date().toISOString(),
service: env.API_NAME || "Cloudflare Worker API",
security: "All security checks passed",
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
break;

case "/time":
// Time information endpoint (from Lab 1.2)
const now = new Date();
response = new Response(
JSON.stringify({
timestamp: now.toISOString(),
unix: Math.floor(now.getTime() / 1000),
timezone: "UTC",
formatted: now.toUTCString(),
environment: env.ENVIRONMENT || "development",
}),
{
headers: { "Content-Type": "application/json" },
}
);
break;

case "/config":
// Configuration endpoint (from Lab 1.3)
response = new Response(
JSON.stringify({
apiName: env.API_NAME,
apiVersion: env.API_VERSION,
environment: env.ENVIRONMENT,
maxRequestsPerMinute: env.MAX_REQUESTS_PER_MINUTE,
timestamp: new Date().toISOString(),
note: "This endpoint shows non-sensitive configuration only",
security: "Configuration access logged and monitored",
}),
{
headers: { "Content-Type": "application/json" },
}
);
break;

case "/secrets":
// Secrets demonstration endpoint (from Lab 1.3)
const hasSecretKey = !!env.API_SECRET_KEY;
const hasAdminToken = !!env.ADMIN_TOKEN;

response = new Response(
JSON.stringify({
secretsAvailable: {
apiSecretKey: hasSecretKey,
adminToken: hasAdminToken,
},
secretKeyLength: env.API_SECRET_KEY
? env.API_SECRET_KEY.length
: 0,
secretKeyPreview: env.API_SECRET_KEY
? env.API_SECRET_KEY.substring(0, 4) + "****"
: "not set",
timestamp: new Date().toISOString(),
note: "Secrets are encrypted and never fully exposed in responses",
security: "Secret access is logged and audited",
}),
{
headers: { "Content-Type": "application/json" },
}
);
break;

default:
// Enhanced 404 response with security
response = new Response(
JSON.stringify({
error: "Not Found",
path: path,
method: method,
timestamp: new Date().toISOString(),
availableEndpoints: [
"GET /",
"GET /info",
"GET /health",
"GET /time",
"GET /config",
"GET /secrets",
],
}),
{
status: 404,
headers: { "Content-Type": "application/json" },
}
);
}

// Apply CORS and security headers to all responses
const headers = new Headers(response.headers);
Object.entries(corsHeaders(origin)).forEach(([key, value]) => {
headers.set(key, value);
});
Object.entries(securityHeaders()).forEach(([key, value]) => {
headers.set(key, value);
});

return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: headers,
});
} catch (error) {
// Production error handling
console.error("Worker error:", error);
return new Response(
JSON.stringify({
error: "Internal Server Error",
timestamp: new Date().toISOString(),
environment: env.ENVIRONMENT || "unknown",
}),
{
status: 500,
headers: {
"Content-Type": "application/json",
...corsHeaders(origin),
...securityHeaders(),
},
}
);
}
},
};

Step 2: Test Your Production-Ready API

Test your enhanced API locally:

npx wrangler dev

Test all endpoints with CORS and security:

  1. GET http://localhost:8787/ - Foundation endpoint with security info
  2. OPTIONS http://localhost:8787/ - CORS preflight handling
  3. All previous endpoints now include security headers
  4. Error handling with proper status codes

curl-option

Step 3: Deploy Your Production API

Deploy your final production-ready Worker:

npx wrangler deploy

Lab 1.4 Summary

What you've built: Completed your production-ready API with:

  • CORS configuration for cross-origin requests
  • Security headers (XSS protection, content type options, etc.)
  • Comprehensive error handling with try-catch
  • Production-ready response structure
  • All 6 endpoints from previous labs working together
  • Testing strategy and structure

🎉 Final Result: A complete, secure, configurable API Worker built progressively through 4 connected labs!


Progressive Learning Summary

Congratulations! You've completed a comprehensive introduction to Cloudflare Workers through a progressive learning path:

Your Learning Journey

Lab 1.1 - Foundation

  • Worker Handler: The fetch function that processes requests
  • Local Development: Using wrangler dev for testing
  • Edge Deployment: Global deployment with wrangler deploy
  • Project Structure: Understanding Worker file organization

Lab 1.2 - Request Handling

  • Request/Response: HTTP request handling and response creation
  • Environment Variables: Basic configuration management
  • Routing: Handling different URL paths
  • JSON Responses: Structured data handling

Lab 1.3 - Advanced Configuration

  • Bindings: Secure access to external resources
  • Secrets Management: Storing sensitive data securely
  • TypeScript Types: Modern wrangler types approach
  • Configuration Management: wrangler.jsonc best practices

Lab 1.4 - Security & Testing

  • Security Practices: Input validation and error handling
  • CORS Configuration: Cross-origin request management
  • Comprehensive Testing: End-to-end Worker validation

Skills Acquired

  • Development Workflow: Local development → Testing → Deployment
  • HTTP Handling: Request parsing, routing, and response management
  • Configuration Management: Environment variables, secrets, and bindings
  • Security Implementation: Input validation, error handling, and CORS
  • Modern TypeScript: Auto-generated types with wrangler types

Next Steps

With this solid foundation, you're ready to build practical Workers for security automation! In the upcoming labs, you'll apply these concepts to the next one.

Each lab built upon the previous one, giving you a complete understanding of the Cloudflare Workers ecosystem. You're now equipped to build production-ready serverless applications at the edge!