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.
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:
npm install -g wrangler
# Verify
wrangler --version
# Should print 3.x or higherNow log in. This opens a browser for OAuth, creates the Cloudflare account if you don't have one, and stores the credentials locally:
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:
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-apiThe 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:
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:
npm run dev
# ⛅️ wrangler 3.x
# [wrangler:inf] Ready on http://localhost:8787In 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:
# 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)"}Deploy to the World — One Command
When local works, push it global:
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.