Cloudflare Workers Tutorial for Beginners: Build Your First API in 10 Minutes

A practical, step-by-step guide to building and deploying your first serverless API. No prior serverless experience needed—if you know JavaScript, you can do this.

Last verified: • All code tested with Wrangler 3.x

What is Cloudflare Workers? (30-Second Explanation)

Cloudflare Workers lets you run JavaScript code on Cloudflare's global network—in 330+ cities worldwide. When someone in Tokyo requests your API, the code runs in Tokyo. Someone in São Paulo? It runs in São Paulo.

🎯 Why Developers Choose Workers

  • Zero cold starts — Code starts in under 5ms (Lambda: 500ms-10s)
  • Global by default — Deploy once, run everywhere
  • Generous free tier — 100,000 requests/day, no credit card needed
  • Pay for CPU only — Not billed while waiting on database/API calls

If you've used Express.js, AWS Lambda, or Vercel Edge Functions, you'll feel right at home. The concepts transfer directly.

💡 Real Talk: Teams regularly report reducing API latency from 180ms to under 15ms just by moving from regional cloud to Workers. The code change takes hours. The performance improvement is permanent.

Prerequisites

You'll need:

  • Node.js 18+Download here
  • A Cloudflare accountSign up free (no credit card required)
  • Basic JavaScript knowledge — If you can write a function, you're ready
  • A code editor — VS Code recommended

Check your Node.js version:

$ node --version
v20.10.0 ✓

1Install Wrangler CLI

Wrangler is Cloudflare's command-line tool for Workers development. Think of it like create-react-app but for serverless APIs.

$ npm install -g wrangler

Verify installation:

$ wrangler --version
⛅️ wrangler 3.91.0

⚠️ Permission Error on Mac/Linux?

If you get EACCES errors, either use sudo npm install -g wrangler or configure npm to use a different directory. See npm's guide.

2Authenticate with Cloudflare

Connect Wrangler to your Cloudflare account:

$ wrangler login

This opens your browser. Click "Allow" to authorize Wrangler. You'll see:

Successfully logged in.

Your credentials are stored locally. You only need to do this once per machine.

3Create Your First Worker

Let's create a new project:

$ npm create cloudflare@latest my-first-worker

When prompted:

  • What type of application? → Select "Hello World" Worker
  • TypeScript or JavaScript? → Choose TypeScript (recommended) or JavaScript
  • Use git? → Yes (recommended)
  • Deploy now? → No (we'll explore the code first)

Navigate into your project:

$ cd my-first-worker

Your project structure:

my-first-worker/
├── src/
│   └── index.ts       ← Your Worker code lives here
├── wrangler.toml      ← Configuration file
├── package.json
└── tsconfig.json

4Understanding the Code

Open src/index.ts. You'll see something like:

src/index.ts
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    return new Response('Hello World!');
  },
};

Let's break this down:

🔍 The Anatomy of a Worker

  • fetch() — The main handler, called for every HTTP request
  • request — The incoming HTTP request (URL, headers, body)
  • env — Environment variables and bindings (KV, D1, R2)
  • ctx — Execution context for background tasks
  • Response — What you return to the client

If you've used Express.js, this is similar to:

// Express equivalent
app.get('*', (req, res) => {
  res.send('Hello World!');
});

The difference? Workers use the standard Web APIs (Request, Response, fetch) that work in browsers. No Express-specific knowledge needed.

5Build a Real API

Let's build something useful: a simple REST API with multiple routes.

Replace your src/index.ts with:

src/index.ts
interface Env {
  // Add your bindings here (KV, D1, R2, etc.)
}

// Simple in-memory data (use D1 for real apps)
const users = [
  { id: 1, name: 'Alice', city: 'London' },
  { id: 2, name: 'Bob', city: 'Tokyo' },
  { id: 3, name: 'Carlos', city: 'São Paulo' },
];

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);
    const path = url.pathname;
    
    // CORS headers for browser requests
    const headers = {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*',
    };
    
    // Route: GET /
    if (path === '/' && request.method === 'GET') {
      return new Response(JSON.stringify({
        message: 'Welcome to my API!',
        endpoints: ['/users', '/users/:id', '/health'],
        location: request.cf?.city || 'Unknown',
      }), { headers });
    }
    
    // Route: GET /users
    if (path === '/users' && request.method === 'GET') {
      return new Response(JSON.stringify(users), { headers });
    }
    
    // Route: GET /users/:id
    if (path.startsWith('/users/') && request.method === 'GET') {
      const id = parseInt(path.split('/')[2]);
      const user = users.find(u => u.id === id);
      
      if (!user) {
        return new Response(JSON.stringify({ error: 'User not found' }), {
          status: 404,
          headers,
        });
      }
      
      return new Response(JSON.stringify(user), { headers });
    }
    
    // Route: GET /health
    if (path === '/health') {
      return new Response(JSON.stringify({
        status: 'healthy',
        timestamp: new Date().toISOString(),
        edge_location: request.cf?.colo || 'Unknown',
      }), { headers });
    }
    
    // 404 for unknown routes
    return new Response(JSON.stringify({ error: 'Not found' }), {
      status: 404,
      headers,
    });
  },
};

🌍 Notice request.cf?

Cloudflare automatically adds geo data to every request: city, country, timezone, ASN, and more. This is free and works globally. Perfect for personalization, compliance, and analytics.

6Test Locally

Start the local development server:

$ wrangler dev

You'll see:

⎔ Starting local server...
Ready on http://localhost:8787

Now test your endpoints:

$ curl http://localhost:8787/
{"message":"Welcome to my API!","endpoints":["/users","/users/:id","/health"]}

$ curl http://localhost:8787/users
[{"id":1,"name":"Alice","city":"London"},{"id":2,"name":"Bob","city":"Tokyo"},{"id":3,"name":"Carlos","city":"São Paulo"}]

$ curl http://localhost:8787/users/1
{"id":1,"name":"Alice","city":"London"}

Hot reload is automatic. Edit your code, save, and the changes appear instantly. No restart needed.

7Deploy to Production

Ready to go live? One command:

$ wrangler deploy

Output:

Uploaded my-first-worker (1.23 sec)
Published my-first-worker (0.45 sec)
  https://my-first-worker.your-subdomain.workers.dev

That's it. Your API is now live on Cloudflare's global network. Try it from your phone, share the URL with a friend in another country—it works everywhere, instantly.

🎉 What Just Happened?

  • Your code was bundled and uploaded to Cloudflare
  • It's now running in 330+ data centers simultaneously
  • Users in Tokyo get served from Tokyo, users in Lagos from Lagos
  • Zero configuration, zero DevOps, zero servers to manage

What's Next? Level Up Your Worker

You've built a basic API. Here's how to add real functionality:

🗄️ Add a Database (D1)

SQLite at the edge. Store and query data globally with sub-10ms latency.

wrangler d1 create my-database
Learn D1 →

🔐 Add Authentication

JWT validation, session management, OAuth flows—all at the edge.

Clodo Auth Guide →

🤖 Add AI (Workers AI)

Run LLMs, embeddings, and image models directly on the edge. No API keys needed.

const result = await env.AI.run('@cf/meta/llama-2-7b', { prompt: '...' });

📦 Add Storage (R2)

S3-compatible object storage with zero egress fees. Store images, files, backups.

wrangler r2 bucket create my-bucket

5 Common Mistakes to Avoid

1. Using Node.js-specific APIs

Workers use Web APIs, not Node.js APIs. fs, path, process don't exist. Use fetch(), Request, Response instead.

2. Forgetting async/await

All I/O in Workers is async. If you forget await, you'll get [object Promise] instead of actual data.

3. Not handling CORS

Browser requests need CORS headers. Add Access-Control-Allow-Origin to your responses, or handle OPTIONS preflight requests.

4. Blocking the event loop

Workers have a 30-second limit (10ms CPU on free tier). Don't run expensive loops or blocking operations. Use ctx.waitUntil() for background tasks.

5. Hardcoding secrets

Never put API keys in your code. Use wrangler secret put SECRET_NAME and access via env.SECRET_NAME.

Frequently Asked Questions

Is Cloudflare Workers really free?

Yes! The free tier includes 100,000 requests/day, 10ms CPU time per request, and access to Workers KV. Most personal projects and MVPs never exceed this. The paid plan ($5/month) adds 10 million requests and more CPU time.

How does Workers compare to AWS Lambda?

Key differences: Workers start in <5ms (Lambda: 500ms-10s cold starts), run in 330+ cities (Lambda: ~30 regions), and bill only CPU time (Lambda: wall-clock time). For API workloads, Workers is typically 3-10x cheaper with 5-20x better latency.

Can I use npm packages?

Yes, but packages must not rely on Node.js APIs (fs, net, child_process). Most utility libraries work: lodash, date-fns, zod, jose, etc. Cloudflare provides compatibility flags for some Node.js APIs.

How do I connect to my existing database?

Workers can connect to any database with an HTTP API. For traditional databases (PostgreSQL, MySQL), use Cloudflare's Hyperdrive for connection pooling, or D1 for a native edge database.

Can I run Python/Go/Rust?

Workers natively run JavaScript/TypeScript. For other languages, use WebAssembly (Wasm). Rust compiles to Wasm directly. Python via Pyodide. Go via TinyGo. Performance is excellent.

📚 Sources & Further Reading

  1. Official Cloudflare Workers Documentation — Comprehensive API reference
  2. Wrangler CLI Documentation — Full command reference
  3. Workers Examples Gallery — Production-ready code snippets
  4. Cloudflare Workers: The Infrastructure Revolution — Deep dive into the platform architecture