Workers Best Practices·续2

const result = await env.MY_KV.get(url.pathname); return new Response(result ?? "Not found", { status: result ? 200 : 404, }); } catch (e) { // ✅ Good: console.error appears as "error" severity in Workers Observability console.error( JSON.stringify({ message: "request failed", error: e instanceof Error ? e.message : String(e), path: url.pathname, }), ); return Response.json({ error: "Internal server error" }, { status: 500 }); } }, };

// 🔴 Bad: unstructured string logs are hard to query const badHandler = { async fetch(request, env) { const url = new URL(request.url); console.log("Got a request to " + url.pathname); return new Response("OK"); }, };


*src/index.tsts*

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

try {
            // ✅ Good: structured JSON — searchable and filterable in the dashboard
            console.log(
                JSON.stringify({
                    message: "incoming request",
                    method: request.method,
                    path: url.pathname,
                }),
            );

const result = await env.MY_KV.get(url.pathname);
            return new Response(result ?? "Not found", {
                status: result ? 200 : 404,
            });
        } catch (e) {
            // ✅ Good: console.error appears as "error" severity in Workers Observability
            console.error(
                JSON.stringify({
                    message: "request failed",
                    error: e instanceof Error ? e.message : String(e),
                    path: url.pathname,
                }),
            );
            return Response.json({ error: "Internal server error" }, { status: 500 });
        }
    },
} satisfies ExportedHandler<Env>;

// 🔴 Bad: unstructured string logs are hard to query
const badHandler = {
    async fetch(request: Request, env: Env): Promise<Response> {
        const url = new URL(request.url);
        console.log("Got a request to " + url.pathname);
        return new Response("OK");
    },
} satisfies ExportedHandler<Env>;

For more information, refer to Workers Logs and Traces.

For more information on all available observability tools, refer to Workers Observability.

Code patterns

Do not store request-scoped state in global scope

Workers reuse isolates across requests. A variable set during one request is still present during the next. This causes cross-request data leaks, stale state, and "Cannot perform I/O on behalf of a different request" errors.

Pass state through function arguments or store it on env bindings. Never in module-level variables.

src/index.jsjs

// 🔴 Bad: global mutable state leaks between requests
let currentUser = null;

const badHandler = {
    async fetch(request, env, ctx) {
        // Storing request-scoped data globally means the next request sees stale data
        currentUser = request.headers.get("X-User-Id");
        const result = await handleRequest(currentUser, env);
        return Response.json(result);
    },
};

// ✅ Good: pass request-scoped data through function arguments
export default {
    async fetch(request, env, ctx) {
        const userId = request.headers.get("X-User-Id");
        const result = await handleRequest(userId, env);

return Response.json(result);
    },
};

async function handleRequest(userId, env) {
    return { userId };
}

src/index.tsts

// 🔴 Bad: global mutable state leaks between requests
let currentUser: string | null = null;

const badHandler = {
    async fetch(
        request: Request,
        env: Env,
        ctx: ExecutionContext,
    ): Promise<Response> {
        // Storing request-scoped data globally means the next request sees stale data
        currentUser = request.headers.get("X-User-Id");
        const result = await handleRequest(currentUser, env);
        return Response.json(result);
    },
} satisfies ExportedHandler<Env>;

// ✅ Good: pass request-scoped data through function arguments
export default {
    async fetch(
        request: Request,
        env: Env,
        ctx: ExecutionContext,
    ): Promise<Response> {
        const userId = request.headers.get("X-User-Id");
        const result = await handleRequest(userId, env);

return Response.json(result);
    },
} satisfies ExportedHandler<Env>;

async function handleRequest(userId: string | null, env: Env): Promise<object> {
    return { userId };
}

For more information, refer to Workers errors.

Always await or waitUntil your Promises

A Promise that is not awaited, returned, or passed to ctx.waitUntil() is a floating promise. Floating promises cause silent bugs: dropped results, swallowed errors, and unfinished work. The Workers runtime may terminate your isolate before a floating promise completes.

Choose based on whether the response depends on the work. Use await or return for work that must complete before the response is correct. Use ctx.waitUntil() for work that can run after the response is sent and can finish within the waitUntil() time limit.

Enable the no-floating-promises lint rule to catch these at development time. If you use ESLint, enable @typescript-eslint/no-floating-promises ↗︎. If you use oxlint, enable typescript/no-floating-promises ↗︎.

# ESLint (typescript-eslint)
npx eslint --rule '{"@typescript-eslint/no-floating-promises": "error"}' src/

# oxlint
npx oxlint --deny typescript/no-floating-promises src/

src/index.jsjs

export default {
    async fetch(request, env, ctx) {
        const data = await request.json();

// 🔴 Bad: floating promise — result is dropped, errors are swallowed
        fetch("https://api.example.com/webhook", {
            method: "POST",
            body: JSON.stringify(data),
        });

// ✅ Good: await if you need the result before responding
        const response = await fetch("https://api.example.com/process", {
            method: "POST",
            body: JSON.stringify(data),
        });

// ✅ Good: waitUntil if you do not need the result before responding
        ctx.waitUntil(
            fetch("https://api.example.com/webhook", {
                method: "POST",
                body: JSON.stringify(data),
            }),
        );

return new Response("OK");
    },
};

src/index.tsts

export default {
    async fetch(
        request: Request,
        env: Env,
        ctx: ExecutionContext,
    ): Promise<Response> {
        const data = await request.json();

// 🔴 Bad: floating promise — result is dropped, errors are swallowed
        fetch("https://api.example.com/webhook", {
            method: "POST",
            body: JSON.stringify(data),
        });

// ✅ Good: await if you need the result before responding
        const response = await fetch("https://api.example.com/process", {
            method: "POST",
            body: JSON.stringify(data),
        });

// ✅ Good: waitUntil if you do not need the result before responding
        ctx.waitUntil(
            fetch("https://api.example.com/webhook", {
                method: "POST",
                body: JSON.stringify(data),
            }),
        );

return new Response("OK");
    },
} satisfies ExportedHandler<Env>;

Security

Use Web Crypto for secure token generation

The Workers runtime provides the Web Crypto API for cryptographic operations. Use crypto.randomUUID() for unique identifiers and crypto.getRandomValues() for random bytes. Never use Math.random() for anything security-sensitive. It is not cryptographically secure.

Node.js node:crypto is also fully supported when nodejs_compat is enabled, so you can use whichever API you or your libraries prefer.

src/index.jsjs

export default {
    async fetch(request, env) {
        // 🔴 Bad: Math.random() is predictable and not suitable for security
        const badToken = Math.random().toString(36).substring(2);

// ✅ Good: cryptographically secure random UUID
        const sessionId = crypto.randomUUID();

// ✅ Good: cryptographically secure random bytes for tokens
        const tokenBytes = new Uint8Array(32);
        crypto.getRandomValues(tokenBytes);
        const token = Array.from(tokenBytes)
            .map((b) => b.toString(16).padStart(2, "0"))
            .join("");

return Response.json({ sessionId, token });
    },
};

src/index.tsts

export default {
    async fetch(request: Request, env: Env): Promise<Response> {
        // 🔴 Bad: Math.random() is predictable and not suitable for security
        const badToken = Math.random().toString(36).substring(2);

// ✅ Good: cryptographically secure random UUID
        const sessionId = crypto.randomUUID();

// ✅ Good: cryptographically secure random bytes for tokens
        const tokenBytes = new Uint8Array(32);
        crypto.getRandomValues(tokenBytes);
        const token = Array.from(tokenBytes)
            .map((b) => b.toString(16).padStart(2, "0"))
            .join("");

return Response.json({ sessionId, token });
    },
} satisfies ExportedHandler<Env>;

When comparing secret values (API keys, tokens, HMAC signatures), use crypto.subtle.timingSafeEqual() to prevent timing side-channel attacks. Do not short-circuit on length mismatch. Encode both values to a fixed-size hash first.

src/verify.jsjs

async function verifyToken(provided, expected) {
    const encoder = new TextEncoder();

// ✅ Good: hash both values to a fixed size, then compare in constant time
    // This avoids leaking the length of the expected value
    const [providedHash, expectedHash] = await Promise.all([
        crypto.subtle.digest("SHA-256", encoder.encode(provided)),
        crypto.subtle.digest("SHA-256", encoder.encode(expected)),
    ]);

return crypto.subtle.timingSafeEqual(providedHash, expectedHash);
}

// 🔴 Bad: direct string comparison leaks timing information
function verifyTokenInsecure(provided, expected) {
    return provided === expected;
}

src/verify.tsts

async function verifyToken(
    provided: string,
    expected: string,
): Promise<boolean> {
    const encoder = new TextEncoder();

// ✅ Good: hash both values to a fixed size, then compare in constant time
    // This avoids leaking the length of the expected value
    const [providedHash, expectedHash] = await Promise.all([
        crypto.subtle.digest("SHA-256", encoder.encode(provided)),
        crypto.subtle.digest("SHA-256", encoder.encode(expected)),
    ]);

return crypto.subtle.timingSafeEqual(providedHash, expectedHash);
}

// 🔴 Bad: direct string comparison leaks timing information
function verifyTokenInsecure(provided: string, expected: string): boolean {
    return provided === expected;
}

Do not use passThroughOnException as error handling

passThroughOnException() is a fail-open mechanism that sends requests to your origin when your Worker throws an unhandled exception. While it can be useful during migration from an origin server, it hides bugs and makes debugging difficult. Use explicit try...catch blocks with structured error responses instead.

src/index.jsjs

// 🔴 Bad: hides errors by falling through to origin
const badHandler = {
    async fetch(request, env, ctx) {
        ctx.passThroughOnException();
        const result = await handleRequest(request, env);
        return Response.json(result);
    },
};

// ✅ Good: explicit error handling with structured responses
export default {
    async fetch(request, env, ctx) {
        try {
            const result = await handleRequest(request, env);
            return Response.json(result);
        } catch (error) {
            const message = error instanceof Error ? error.message : "Unknown error";

console.error(
                JSON.stringify({
                    message: "unhandled error",
                    error: message,
                    path: new URL(request.url).pathname,
                }),
            );

return Response.json({ error: "Internal server error" }, { status: 500 });
        }
    },
};

async function handleRequest(request, env) {
    return { status: "ok" };
}

src/index.tsts

// 🔴 Bad: hides errors by falling through to origin
const badHandler = {
    async fetch(
        request: Request,
        env: Env,
        ctx: ExecutionContext,
    ): Promise<Response> {
        ctx.passThroughOnException();
        const result = await handleRequest(request, env);
        return Response.json(result);
    },
} satisfies ExportedHandler<Env>;

// ✅ Good: explicit error handling with structured responses
export default {
    async fetch(
        request: Request,
        env: Env,
        ctx: ExecutionContext,
    ): Promise<Response> {
        try {
            const result = await handleRequest(request, env);
            return Response.json(result);
        } catch (error) {
            const message = error instanceof Error ? error.message : "Unknown error";

console.error(
                JSON.stringify({
                    message: "unhandled error",
                    error: message,
                    path: new URL(request.url).pathname,
                }),
            );

return Response.json({ error: "Internal server error" }, { status: 500 });
        }
    },
} satisfies ExportedHandler<Env>;

async function handleRequest(request: Request, env: Env): Promise<object> {
    return { status: "ok" };
}

Development and testing

Test with @cloudflare/vitest-plugin

The @cloudflare/vitest-plugin package runs your tests inside the Workers runtime, giving you access to real bindings (KV, R2, D1, Durable Objects) during tests. This catches issues that Node.js-based tests miss, like unsupported APIs or missing compatibility flags.

One known pitfall: the Vitest plugin automatically injects nodejs_compat, so tests pass even if your Wrangler configuration does not have the flag. Always confirm your wrangler.jsonc includes nodejs_compat if your code depends on Node.js built-in modules.

test/index.test.jsjs

import { describe, it, expect } from "vitest";
import { env } from "cloudflare:workers";

describe("KV operations", () => {
    it("should store and retrieve a value", async () => {
        await env.MY_KV.put("key", "value");
        const result = await env.MY_KV.get("key");
        expect(result).toBe("value");
    });

it("should return null for missing keys", async () => {
        const result = await env.MY_KV.get("nonexistent");
        // ✅ Good: test the null case explicitly
        expect(result).toBeNull();
    });
});

test/index.test.tsts

import { describe, it, expect } from "vitest";
import { env } from "cloudflare:workers";

describe("KV operations", () => {
    it("should store and retrieve a value", async () => {
        await env.MY_KV.put("key", "value");
        const result = await env.MY_KV.get("key");
        expect(result).toBe("value");
    });

it("should return null for missing keys", async () => {
        const result = await env.MY_KV.get("nonexistent");
        // ✅ Good: test the null case explicitly
        expect(result).toBeNull();
    });
});

For more information, refer to Testing with Vitest.

Was this helpful?

YesNo

On this page

Workers Best Practices·续2 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workers/best-practices/workers-best-practices/#page","headline":"Workers Best Practices","description":"Code patterns and configuration guidance for building fast, reliable, observable, and secure Workers.","url":"https://developers.cloudflare.com/workers/best-practices/workers-best-practices/","inLanguage":"en","image":"https://developers.cloudflare.com/workers/best-practices/workers-best-practices/og.png?v=d435c8b7aebe8dd9","dateModified":"2026-10-12","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Explore demo applications and reference architectures built with Cloudflare Workers. title: Demos and architectures image: https://developers.cloudflare.com/workers/demos/og.png?v=0cfc971b5d74a7ce


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt
Use this file to discover all available pages before exploring further.