Starting a Cloudflare Worker from scratch

While tooling for web development is getting more and more complex, with CLIs for your CLIs becoming a thing, sometimes it's worth taking a step back, and realizing that doing things from scratch isn't so hard.

In this article, we're going to create and deploy a Cloudflare Worker without a CLI.

Table of contents

Steps to "Hello World"

  1. Make a new directory, and cd into it
mkdir my-worker/
cd my-worker/
  1. Initialize the npm project
npm init -y
  1. Install some packages
npm install -D typescript wrangler @cloudflare/workers-types
  1. Create a tsconfig.json file
touch tsconfig.json

After running the above command, paste the following into tsconfig.json:

"compilerOptions": {
"noEmit": true,
"module": "esnext",
"target": "esnext",
"lib": ["esnext"],
"strict": true,
"moduleResolution": "node",
"types": ["@cloudflare/workers-types"]
  1. Make a src/ directory, create an index.ts file inside
mkdir src/
touch src/index.ts

After running the above command, paste the following into index.ts:

// index.ts
export default {
fetch: () => {
return new Response('Hello World!');
  1. Create a wrangler.toml file
touch wrangler.toml

After running the above command, paste the following into wrangler.toml:

# wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2023-12-22"
  1. Login to wrangler
npx wrangler login
  1. Deploy your Worker
npx wrangler deploy

You can now visit the URL that wrangler outputs, and you should see "Hello World" in your browser.

  1. (optionally) Install vitest, add a test script
npm install vitest -D
touch src/index.test.ts

After running the above commands, paste the following into src/index.test.ts:

import { unstable_dev } from 'wrangler';
import type { UnstableDevWorker } from 'wrangler';
import { describe, expect, it, beforeAll, afterAll } from 'vitest';
describe('Worker', () => {
let worker: UnstableDevWorker;
beforeAll(async () => {
worker = await unstable_dev('src/index.ts', {
experimental: { disableExperimentalWarning: true },
afterAll(async () => {
await worker.stop();
it('should return Hello World', async () => {
const resp = await worker.fetch();
if (resp) {
const text = await resp.text();
expect(text).toMatchInlineSnapshot(`"Hello World!"`);

Finally, in package.json replace the test script with:

"test": "vitest"


There you have it, a Cloudflare Worker from scratch.

While it's not strictly better than just running

npx wrangler@2 init my-worker2 -y

(which is the exact CLI command you'd run to get the project above)

At least this way you understand what's happening before reaching for tooling to automate it.