Back to Blog
Lesson 50 of the Cloudflare: Cloudflare for Developers: DNS to CDN course
Cloud NativeAugust 29, 20263 min read

Edge Logic Best Practices: Writing Maintainable Workers

Learn to write efficient, clean, and maintainable Edge logic. We’ll cover architectural patterns for Cloudflare Workers to keep your app performant.

CloudflareWorkersEdge ComputingArchitectureBest Practices
A skilled construction worker in protective gear hammering a rooftop panel.

Previously in this course, we explored Global State Management and the trade-offs of distributed systems. This lesson adds a layer of architectural discipline, showing you how to structure your Workers to remain maintainable as your codebase grows.

Writing code that runs on the edge is fundamentally different from traditional server-side development. You aren't just writing an API; you are writing distributed middleware that executes in milliseconds across hundreds of data centers.

Principles of Efficient Edge Logic

When you deploy logic to the edge, your constraints are execution time, memory footprint, and cold-start latency. Unlike a long-running Node.js process, a Worker is ephemeral.

Keep the "Main" Lean

Your entry point (the fetch handler) should act as a router, not a business logic container. If you find your fetch function exceeding 50 lines, you need to extract your concerns into separate modules or classes.

The "Single Responsibility" Worker

In Project Milestone: The Dynamic Backend, we linked R2 and D1. If that Worker also started handling authentication, image processing, and logging, it would become a "God Object." Instead, treat your Worker as a composition of smaller, pure functions.

Worked Example: Modularizing Edge Logic

A contemporary 3D geometric pattern with futuristic design elements in muted tones.

Let's refactor a common pattern: fetching data from D1 and returning it with custom headers. Instead of putting everything in index.js, we separate concerns.

JAVASCRIPT
// utils/db.js - Database interactions only
export async function getAssetMetadata(db, id) {
  return await db.prepare("SELECT * FROM assets WHERE id = ?").bind(id).first();
}

// utils/headers.js - Header manipulation
export function addSecurityHeaders(response) {
  const newResponse = new Response(response.body, response);
  newResponse.headers.set("X-Content-Type-Options", "nosniff");
  return newResponse;
}

// index.js - The clean entry point
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const id = url.pathname.split(CE9178">'/').pop();
    
    const data = await getAssetMetadata(env.DB, id);
    const response = Response.json(data);
    
    return addSecurityHeaders(response);
  }
};

This approach makes testing easier. When writing unit tests for Workers, you can import getAssetMetadata independently without mocking the entire fetch event.

Common Pitfalls at the Edge

Even experienced engineers trip over these three common issues:

  1. Over-fetching data: If your D1 query returns 50 columns but you only need two, you are wasting CPU cycles and memory. Always use explicit SELECT statements.
  2. Blocking the event loop: Do not perform heavy synchronous tasks. Every operation that can be asynchronous (like network requests to APIs) must be await-ed properly.
  3. Ignoring the "Cold Start": If you use massive libraries (like heavy ORMs), your cold start time will skyrocket. Stick to lightweight, tree-shakable dependencies.

Hands-on Exercise: Refactor for Maintainability

  1. Open your project from the Dynamic Backend milestone.
  2. Identify one function inside your fetch handler that handles external data or formatting.
  3. Move that function to a lib/ or utils/ directory.
  4. Export the function and update your index.js to import it.
  5. Run wrangler dev and verify that the behavior remains identical.

FAQ: Edge Logic Best Practices

  • Should I use an ORM? Use extreme caution. Only use ORMs that are explicitly designed for Workers or are lightweight enough to not impact cold starts significantly.
  • How do I handle errors across modules? Create a centralized error-handling helper that accepts an error object and returns a standardized JSON response, ensuring your API surface remains consistent.
  • Is it better to have one big Worker or many small ones? One "big" Worker using modular code is usually easier to manage and deploy than dozens of micro-Workers, unless you have distinct teams managing specific routes.

Recap

Edge logic is all about discipline. By keeping your fetch handler slim, modularizing your helper functions, and avoiding heavy dependencies, you ensure your application remains performant at global scale. Remember, you aren't just writing code; you are orchestrating global traffic.

Up next: We'll dive into advanced deployment strategies, moving beyond simple pushes into blue-green deployments at the edge.

Similar Posts