<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Chike Ozulumba]]></title><description><![CDATA[Chike Ozulumba]]></description><link>https://blog.chikeozulumba.com</link><generator>RSS for Node</generator><lastBuildDate>Thu, 30 Apr 2026 00:32:07 GMT</lastBuildDate><atom:link href="https://blog.chikeozulumba.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Migrate an Express.js API to Cloudflare Workers]]></title><description><![CDATA[Originally published at my personal website
Migrating from Express.js to Cloudflare Workers represents a fundamental shift from server-based computing to edge computing. This document provides an in-depth analysis of the migration process, architectu...]]></description><link>https://blog.chikeozulumba.com/migrate-an-expressjs-api-to-cloudflare-workers</link><guid isPermaLink="true">https://blog.chikeozulumba.com/migrate-an-expressjs-api-to-cloudflare-workers</guid><category><![CDATA[cloudflare]]></category><category><![CDATA[cloudflare-worker]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[honojs]]></category><dc:creator><![CDATA[Chike Ozulumba]]></dc:creator><pubDate>Sat, 24 Jan 2026 00:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769528461269/37c334dc-4558-48c7-b11b-44ab8241210a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Originally published at my</em> <a target="_blank" href="https://chikeozulumba.com/blog/migrate-an-expressjs-api-to-cloudflare-workers/"><em>personal website</em></a></p>
<p>Migrating from Express.js to Cloudflare Workers represents a fundamental shift from server-based computing to edge computing. This document provides an in-depth analysis of the migration process, architectural considerations, technical challenges, and strategic decision-making frameworks to ensure a successful transition.</p>
<h2 id="heading-why-migrate-to-workers">Why migrate to Workers?</h2>
<ul>
<li><p><strong>Performance and reach</strong>: This makes your API accessible in more than 300 locations worldwide.</p>
</li>
<li><p><strong>Latency</strong>: Your app experiences zero cold starts. Workers spin up in under 5ms on average.</p>
</li>
<li><p><strong>Cost efficiency</strong>: You get to pay for only requests and not idle server time</p>
</li>
<li><p><strong>Automatic scaling</strong>: Handle traffic spikes with zero configuration</p>
</li>
</ul>
<h2 id="heading-understanding-the-differences">Understanding the Differences</h2>
<h3 id="heading-traditional-server-architecture-expressjs">Traditional Server Architecture (Express.js)</h3>
<p>Express.js applications run on centralized servers with the following characteristics:</p>
<ul>
<li><p><strong>Runtime Environment</strong>: Node.js provides a full operating system environment with access to file systems, network sockets, and long-running processes. Applications maintain state in memory across requests, enabling session management, connection pooling, and background job processing.</p>
</li>
<li><p><strong>Request Lifecycle</strong>: When a request arrives, it traverses through a middleware stack where each function can modify the request, perform operations, and pass control to the next middleware. The server maintains context throughout this chain, enabling complex request-processing patterns.</p>
</li>
<li><p><strong>Resource Management</strong>: Servers allocate dedicated resources, including memory, CPU, and network connections. Database connections are typically pooled and reused across requests. The application lifecycle includes startup, runtime, and graceful shutdown phases.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769528458400/e5f588e3-1834-495e-99ab-e40c90688cb3.png" alt="Traditional Server Architecture (Express.js)" /></p>
<h3 id="heading-edge-computing-architecture-workers">Edge Computing Architecture (Workers)</h3>
<p>Workers operate on a fundamentally different model:</p>
<ul>
<li><p><strong>Distributed Execution</strong>: Code runs in over 300 data centres globally. Each request is routed to the nearest location to minimise latency. There's no single server; instead, your code exists as a distributed system automatically.</p>
</li>
<li><p><strong>Isolate-based Runtime</strong>: Instead of spinning up containers or VMs, Workers use V8 isolates—lightweight execution contexts that start in under 5ms. Each request gets its own isolated environment, which is destroyed after completion.</p>
</li>
<li><p><strong>Stateless by Default</strong>: Workers don't maintain state between requests. Every invocation starts fresh. This requires rethinking session management, caching strategies, and data persistence patterns.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769528460121/b77e0181-b0c3-4ea3-a1e3-acd0d1b13e92.png" alt="Edge Computing Architecture (Workers)" /></p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we get started, the following requirements are essential to follow along:</p>
<ul>
<li><p>An existing Express.js application</p>
</li>
<li><p>Node.js and npm installed</p>
</li>
<li><p>A <a target="_blank" href="https://www.cloudflare.com/">Cloudflare</a> account</p>
</li>
<li><p><a target="_blank" href="https://developers.cloudflare.com/workers/wrangler/install-and-update/">Wrangler CLI</a> - install via <code>npm install -g wrangler</code> on your local machine</p>
</li>
</ul>
<h2 id="heading-migration-techniques">Migration Techniques</h2>
<p>There are several techniques you can use to migrate your Express.js application to Cloudflare Workers. In this article, we will be using the following technique:</p>
<h3 id="heading-using-expressjs-before-migration">Using Express.js (before migration)</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();

app.use(express.json());

app.get(<span class="hljs-string">'/api/health'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.json({ <span class="hljs-attr">status</span>: <span class="hljs-string">'healthy'</span> });
});

app.post(<span class="hljs-string">'/api/users'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { name, email } = req.body;
  res.status(<span class="hljs-number">201</span>).json({ <span class="hljs-attr">id</span>: <span class="hljs-number">123</span>, name, email });
});

app.get(<span class="hljs-string">'/api/users/:id'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.json({ <span class="hljs-attr">id</span>: req.params.id, <span class="hljs-attr">name</span>: <span class="hljs-string">'Jane Doe'</span> });
});
</code></pre>
<h3 id="heading-using-hono">Using Hono</h3>
<p><a target="_blank" href="https://hono.dev/">Hono</a> is a lightweight framework for building web applications. It is a great choice for migrating your Express.js application to Cloudflare Workers because it is built on top of Cloudflare Workers and is a great way to get started. It is an Express-like framework designed for edge environments and provides familiar routing patterns with minimal refactoring.</p>
<h3 id="heading-using-hono-on-workers">Using Hono on Workers</h3>
<p>To get started with Hono on Workers, you can create a new Worker project using the following command:</p>
<pre><code class="lang-bash">wrangler init my-worker
</code></pre>
<p>This will create a new Worker project with a default <code>wrangler.toml</code> file and a <code>index.ts</code> file.</p>
<p>You can then start the Worker locally using the following command:</p>
<pre><code class="lang-bash">wrangler dev
</code></pre>
<p>This will start the Worker locally on port 8787. You can then access the Worker at <code>http://localhost:8787</code>.</p>
<p>To deploy the Worker to Cloudflare, you can use the following command:</p>
<pre><code class="lang-bash">wrangler deploy
</code></pre>
<p>This will deploy the Worker to Cloudflare and you can then access the Worker at <code>https://your-worker-name.cloudflare.workers.dev</code>.</p>
<p>You can then access the Worker at <code>https://your-worker-name.cloudflare.workers.dev</code>.</p>
<p>To get the Worker's URL, you can use the following command:</p>
<pre><code class="lang-bash">wrangler url
</code></pre>
<p>This will return the Worker's URL.</p>
<p>You can then use the Worker's URL in your application to access the Worker.</p>
<h3 id="heading-using-hono-on-workers-with-the-hono-package">Using Hono on Workers with the <code>hono</code> package</h3>
<p>Install Hono using the following command:</p>
<pre><code class="lang-bash">npm install hono
</code></pre>
<p>You can also use the <code>hono</code> package to create a Worker. This is a great way to get started with Hono on Workers.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Hono } <span class="hljs-keyword">from</span> <span class="hljs-string">'hono'</span>;

<span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> Hono();

app.get(<span class="hljs-string">'/api/health'</span>, <span class="hljs-function">(<span class="hljs-params">c</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> c.json({ status: <span class="hljs-string">'healthy'</span> });
});

app.post(<span class="hljs-string">'/api/users'</span>, <span class="hljs-keyword">async</span> (c) =&gt; {
  <span class="hljs-keyword">const</span> { name, email } = <span class="hljs-keyword">await</span> c.req.json();
  <span class="hljs-keyword">return</span> c.json({ id: <span class="hljs-number">123</span>, name, email }, <span class="hljs-number">201</span>);
});

app.get(<span class="hljs-string">'/api/users/:id'</span>, <span class="hljs-function">(<span class="hljs-params">c</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> id = c.req.param(<span class="hljs-string">'id'</span>);
  <span class="hljs-keyword">return</span> c.json({ id, name: <span class="hljs-string">'Jane Doe'</span> });
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> app;
</code></pre>
<p>Hono remains a great choice for migrating your Express.js application to Cloudflare Workers because it mimics the Express.js API and syntax while running on the mordern infrastructure of Cloudflare Workers.</p>
<h3 id="heading-replacing-the-existing-expressjs-middleware-with-hono-middleware">Replacing the existing Express.js middleware with Hono middleware</h3>
<p>One common question is how middleware will be handled after migrating from Express.js to Hono. It is importnant to note that they both implement similar middleware patterns.</p>
<h4 id="heading-using-expressjs-middleware">Using Express.js middleware</h4>
<pre><code class="lang-javascript">app.use(<span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${req.method}</span> <span class="hljs-subst">${req.path}</span>`</span>);
  next();
});
</code></pre>
<h4 id="heading-using-hono-middleware">Using Hono middleware</h4>
<pre><code class="lang-ts">app.use(<span class="hljs-function">(<span class="hljs-params">c</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${c.req.method}</span> <span class="hljs-subst">${c.req.path}</span>`</span>);
  <span class="hljs-keyword">return</span> c.next();
});
</code></pre>
<p>As you can see, the middleware patterns are similar. The main difference is that Hono middleware is async and returns a Promise. This is because Hono middleware is executed in a different context than Express.js middleware. There are other libraries that can be used to replace several functionalities found in ExpressJS in Hono, for example:</p>
<ul>
<li><p><code>cors</code> -&gt; <code>hono/cors</code></p>
</li>
<li><p><code>compression</code> -&gt; <code>hono/compress</code></p>
</li>
<li><p><code>jwt</code> -&gt; <code>hono/jwt</code></p>
</li>
<li><p><code>logging</code> -&gt; <code>hono/logger</code></p>
</li>
<li><p>Rate Limiting -&gt; <a target="_blank" href="https://github.com/rhinobase/hono-rate-limiter"><code>https://github.com/rhinobase/hono-rate-limiter</code></a></p>
</li>
</ul>
<h3 id="heading-working-with-environment-variables">Working with environment variables</h3>
<p>In express.js, environment variables are loaded using different libraries, for example: <code>dotenv</code>, <code>config</code>, <code>env-cmd</code>, <code>nconf</code>, etc. In Hono, a utilty method called <code>env</code> is provided to load environment variables across multiple runtimes and services.</p>
<p>In Workers for example, environment variables can be managed through the worker configuration file saved in format of a <code>toml</code> or <code>jsonc</code> file, for example: <code>wrangler.toml</code> or <code>wrangler.jsonc</code>.</p>
<p>In <code>wrangler.toml</code>, you can define environment variables for your Worker like this:</p>
<pre><code class="lang-bash">[vars]
...
API_KEY=...
API_URL=...
APP_ENV=production
...
</code></pre>
<p>Or in <code>wrangler.jsonc</code>, you can define environment variables for your Worker like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"vars"</span>: {
    <span class="hljs-attr">"API_KEY"</span>: <span class="hljs-string">"..."</span>,
    <span class="hljs-attr">"API_URL"</span>: <span class="hljs-string">"..."</span>,
    <span class="hljs-attr">"APP_ENV"</span>: <span class="hljs-string">"production"</span>,
    ... <span class="hljs-comment">// other environment variables</span>
  }
}
</code></pre>
<p>You can then access the environment variables in your Worker using the <code>env</code> method:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Hono } <span class="hljs-keyword">from</span> <span class="hljs-string">'hono'</span>;

<span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> Hono&lt;{ Bindings: Env }&gt;();

app.get(<span class="hljs-string">'/api/health'</span>, <span class="hljs-function">(<span class="hljs-params">c</span>) =&gt;</span> {
 <span class="hljs-keyword">return</span> c.json({ status: <span class="hljs-string">'healthy'</span>, vars: c.env.APP_ENV });
});
... <span class="hljs-comment">// other routes</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> app;
</code></pre>
<p>You can find more information about working with environment variables in <a target="_blank" href="https://hono.dev/docs/runtime/env">Hono</a>, working with <a target="_blank" href="https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables">environment variables in Workers</a> and <a target="_blank" href="https://developers.cloudflare.com/workers/configuration/secrets/">managing secrets in Workers</a>.</p>
<p>In summary, we were able to successfully migrate an Express.js application to Cloudflare Workers using Hono. We were able to use the same middleware patterns, environment variables and routing patterns as we used in Express.js. We were also able to use the same API and syntax as we used in Express.js. This is good strategy at developing modern applications that are fast, scalable and efficient, with the added benefit of running on the edge of the internet.</p>
]]></content:encoded></item></channel></rss>