Last weekend I needed a public endpoint to receive form submissions from a landing page. Nothing fancy — accept a JSON POST, validate a few fields, return a sensible response. The whole thing would handle maybe 50 requests a week. I did not want to put a credit card on file for a $20/month server, learn a new infra tool, or sit through a CI pipeline every time I changed three lines. Ten minutes after I started, the API was live in 300+ cities and costing me zero dollars. This is the walkthrough.

Cloudflare Workers has a free tier that's actually generous: 100,000 requests per day, no credit card required to sign up, and your code runs at the edge instead of in one region. For the small APIs that most side projects need — webhook receivers, form handlers, link shorteners, JSON proxies — you can ship and forget. This guide takes you from "no Cloudflare account" to "live URL responding to curl" in about ten minutes. We'll build a real endpoint: a JSON validator that accepts a POST, checks the payload, and echoes back a clean response.

What You Get for Free (And What You Don't)

The Cloudflare Workers free plan gives you 100,000 requests per day and 10 milliseconds of CPU time per request. That sounds tight until you realise most JSON endpoints — parse body, do a tiny bit of work, return a response — finish in well under 1ms. The 10ms ceiling is CPU time, not wall-clock time, so an upstream fetch that takes 800ms still counts as roughly 1ms of CPU because your code is mostly waiting on I/O.

What you don't get on the free plan: high-throughput persistent storage (Workers KV has its own free quota — generous for most cases, but separate from request count), large response payloads, or scheduled cron triggers more often than once a minute. For a typical "receive JSON, do a thing, return JSON" service that doesn't need a database, you will not hit a wall.

No credit card on signup. When you create the account, Cloudflare asks for an email and a password — that's it. The dashboard does prompt you to add billing later for paid products, but the free Workers tier needs nothing and will not surprise you with a charge.

Install Node.js and Wrangler

Wrangler is Cloudflare's CLI for Workers. Install it via npm — you'll need Node.js 18 or newer. If you've already got Node, this is a one-liner:

bash
npm install -g wrangler

# Verify
wrangler --version
# Should print 3.x or higher

Now log in. This opens a browser for OAuth, creates the Cloudflare account if you don't have one, and stores the credentials locally:

bash
wrangler login

# Opens https://dash.cloudflare.com/oauth/authorize ...
# After you approve: "Successfully logged in."

Scaffold the Project

One command and you've got a project skeleton with sensible defaults — a sample fetch handler, a wrangler.toml config, and the local dev server already wired up:

bash
npm create cloudflare@latest free-json-api

# When prompted, pick:
#   "Hello World" Worker
#   JavaScript (or TypeScript — your call)
#   No deploy yet (we'll do that ourselves)

cd free-json-api

The generator drops everything you need into one folder. Open src/index.js — that's the file your Worker runs. We'll replace its contents with something more useful than the default Hello World.

Write the JSON API

Here's a complete Worker that accepts a JSON POST, validates two required fields, and returns a clean response. It handles the common failure modes — wrong method, malformed JSON, missing fields — instead of crashing into a generic 500:

js
export default {
  async fetch(request) {
    if (request.method !== 'POST') {
      return Response.json(
        { error: 'POST only' },
        { status: 405 },
      );
    }

    let body;
    try {
      body = await request.json();
    } catch {
      return Response.json(
        { error: 'Body must be valid JSON' },
        { status: 400 },
      );
    }

    const { email, message } = body;
    if (typeof email !== 'string' || typeof message !== 'string') {
      return Response.json(
        { error: 'email and message are required (strings)' },
        { status: 422 },
      );
    }

    return Response.json({
      ok: true,
      receivedAt: new Date().toISOString(),
      echo: { email, message },
    });
  },
};

Two things worth pointing out. Response.json() serializes the object and sets Content-Type: application/json for you, so you don't have to remember the header. And wrapping request.json() in a try/catch is the single most important habit when building Workers — without it, a malformed payload returns an opaque 500 from Cloudflare's runtime instead of a useful 400 from your code.

Test It Locally

Wrangler ships with a local dev server that runs the same V8 isolate Cloudflare uses in production. Start it with:

bash
npm run dev

# ⛅️ wrangler 3.x
# [wrangler:inf] Ready on http://localhost:8787

In another terminal, hit it with curl. Try the happy path first, then a deliberately broken payload — you should see clean JSON errors instead of stack traces:

bash
# Happy path
curl -X POST http://localhost:8787 \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","message":"hello"}'

# {"ok":true,"receivedAt":"2025-10-15T...","echo":{...}}

# Broken JSON (note the missing closing brace)
curl -X POST http://localhost:8787 \
  -H "Content-Type: application/json" \
  -d '{"email": "ava"'

# {"error":"Body must be valid JSON"}

# Missing required field
curl -X POST http://localhost:8787 \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'

# {"error":"email and message are required (strings)"}
Tip: if a response looks off in your terminal, paste it into the JSON Formatter to pretty-print and inspect. I do this twenty times a day when working with unfamiliar APIs.

Deploy to the World — One Command

When local works, push it global:

bash
npx wrangler deploy

# Total Upload: 1.18 KiB / gzip: 0.55 KiB
# Uploaded free-json-api (1.34 sec)
# Published free-json-api (4.21 sec)
#   https://free-json-api.<your-subdomain>.workers.dev
# Current Deployment ID: ...

That URL is now live on every Cloudflare data centre. A request from Berlin hits Frankfurt, one from Tokyo hits Tokyo, one from São Paulo hits São Paulo — same code, no extra config. The bundle was 1.2 KiB. Test the live URL with the same curl commands you used locally — just swap localhost:8787 for the workers.dev hostname Wrangler printed.

When You'll Outgrow the Free Tier

For a long time, you won't. 100k requests/day is roughly 1.15 requests per second averaged over 24 hours — comfortably more than most side projects, internal tools, or early-stage products do. The first thing that usually pushes you off the free tier is not request count but storage: if you start writing a lot to KV, R2, or D1, those have their own quotas. The paid plan starts at $5/month and gives you 10 million requests, more CPU per request, and a credit-card safety net for spikes. Most projects never need it.

What's Next

What you've shipped is the bare minimum useful Worker. Real production endpoints need a few more habits — CORS for browser callers, secrets management for API keys, edge caching for slow upstreams, and a strategy for proxying upstream services. I covered those in Cloudflare Workers and JSON — A Practical Guide, which picks up where this article ends.

A few tools I keep open while building Workers that deal with JSON: JSON Formatter for pretty-printing responses, JSON Validator for figuring out exactly why a payload was rejected, and JSON Path for planning field-extraction logic before writing it. The JSON format itself is specified in RFC 8259 — worth a skim once you start handling weird inputs from real users.

For deeper Worker patterns — KV storage, Durable Objects, scheduled triggers, the AI inference bindings — Cloudflare's Workers examples gallery is the best resource I've found. It's a copy-pasteable list of recipes for the next steps.

Wrapping Up

Cloudflare Workers is the rare free tier that's actually viable for real side projects. No credit card to start, 100k requests a day, and a deploy story that fits in a single command. The pattern in this article — accept JSON, validate, return JSON — covers a surprising amount of what most APIs do. If you've been holding off on shipping a small API because the infra felt like a chore, this is the path I wish I'd taken three years ago.