{"id":10517,"date":"2026-07-27T22:59:22","date_gmt":"2026-07-28T03:59:22","guid":{"rendered":"https:\/\/master.dev\/blog\/?p=10517"},"modified":"2026-07-27T22:59:23","modified_gmt":"2026-07-28T03:59:23","slug":"cloudflare-workers-and-hyperdrive-with-sveltekit","status":"publish","type":"post","link":"https:\/\/master.dev\/blog\/cloudflare-workers-and-hyperdrive-with-sveltekit\/","title":{"rendered":"Cloudflare Workers and Hyperdrive with SvelteKit"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Let&#8217;s get into web application setup for Cloudflare Workers on <a href=\"https:\/\/svelte.dev\/docs\/kit\/introduction\">SvelteKit<\/a>. I&#8217;ve written about Cloudflare <a href=\"https:\/\/master.dev\/blog\/introduction-to-cloudflare-workers-for-web-apps\/\">where we introduced workers<\/a>, and then where we showed some of the one-off development considerations needed for <a href=\"https:\/\/master.dev\/blog\/cloudflare-workers-and-hyperdrive-with-tanstack-start\/\">getting Workers to work in TanStack Start<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This will be similar to the latter, except we&#8217;ll look at SvelteKit rather than TanStack.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ve found the Cloudflare\/SvelteKit integration not quite as seamless as TanStack&#8217;s, but it&#8217;s still outstanding and doesn&#8217;t take too much effort to get up and running.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What are Cloudflare Workers?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cloudflare Workers are conceptually similar to AWS Lambda functions. They&#8217;re cloud functions that spin up on demand, as much or as little as your application&#8217;s traffic demands at any given moment. Except Cloudflare Workers have very, very low latency. The &#8220;cold starts&#8221; Lambda is known to have are virtually non-existent with Workers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What&#8217;s the Catch?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Not much, really. There was a time when Cloudflare Workers had a runtime that was a subset of Node, but those days are over. Cloudflare Workers now have a Node compat mode that solves those problems.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The main limitation with Workers is that they have special rules requiring you to clean up after yourself in ways other runtimes don&#8217;t. Namely, you cannot have long-running I\/O objects surviving between requests. In particular, you cannot simply have a module export a <code>db<\/code> object that connects to your database. Each request must spin that connection up fresh.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Introducing Hyperdrive<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Spinning up a fresh database connection was always a bad idea in&nbsp;<em>any<\/em>&nbsp;cloud function runtime, such as AWS Lambda. These functions spin up as needed, and during periods of bursting traffic, the number of Lambda functions being created could easily overwhelm your database.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But with Workers requiring a fresh connection <em>per request,<\/em> this is even more dangerous. To say nothing of the fact that we&#8217;d hardly want to ruin Workers&#8217; low latency by requiring them to perform the time-consuming operation of establishing a fresh TCP connection to our database, let alone once <em>per request<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To solve these problems, Cloudflare has a tool called <a href=\"https:\/\/www.cloudflare.com\/products\/hyperdrive\/\">Hyperdrive<\/a>, which keeps a pool of pre-warmed connections to our database open. Our Workers then quickly connect to Hyperdrive and gain immediate access to these pre-warmed connections.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Getting Started<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s scaffold an essentially empty SvelteKit application. We&#8217;ll go to the directory we want our app, and then kick things off.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npx sv create .<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Enabling Cloudflare<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s set up basic Cloudflare infrastructure for our project.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npx wrangler deploy<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">We&#8217;ll be asked a few questions, to which the defaults should be fine.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"612\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0.jpg?resize=1024%2C612&#038;ssl=1\" alt=\"\" class=\"wp-image-10543\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0.jpg?resize=1024%2C612&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0.jpg?resize=300%2C179&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0.jpg?resize=768%2C459&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0.jpg?w=1218&amp;ssl=1 1218w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This will install some new dependencies and set up the Cloudflare plugin.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"604\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0a.jpg?resize=1024%2C604&#038;ssl=1\" alt=\"\" class=\"wp-image-10544\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0a.jpg?resize=1024%2C604&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0a.jpg?resize=300%2C177&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0a.jpg?resize=768%2C453&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img0a.jpg?w=1532&amp;ssl=1 1532w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Very nice!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">One Problem<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If we open our Cloudflare dashboard, we will&nbsp;not&nbsp;see this new app present, and if we look in our terminal, we&#8217;ll see why.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">[build] \u2718 [ERROR] Types file not found at worker-configuration.d.ts.<br>[build]<br>[build]<br>[build]<br>[build] \ud83e\udeb5  Logs were written to \"\/Users\/arackis\/Library\/Preferences\/.wrangler\/logs\/wrangler-2026-07-12_23-14-01_652.log\"<br>[build]<br>\u2718 [ERROR] Error: Command failed with exit code 1: npm run build<br><br>  \u2718 [ERROR] Types file not found at<br>  worker-configuration.d.ts.<br><br><br>  \ud83e\udeb5  Logs were written to<br>  \"\/Users\/arackis\/Library\/Preferences\/.wrangler\/logs\/wrangler-2026-07-12_23-14-01_652.log\"<br><br>  &gt; sveltekit-temp@0.0.1 build<br>  &gt; wrangler types --check &amp;&amp; vite build<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The problem is the <code>build<\/code> task Cloudflare scaffolded us.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-string\">\"build\"<\/span>: <span class=\"hljs-string\">\"vite build &amp;&amp; wrangler types --check\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The problem is the latter piece: <code>wrangler types --check<\/code>. This asks Wrangler to confirm that the generated typings are fully aligned with the application&#8217;s needs. In my experience, this is a fickle check that fails for reasons you may not care about, such as secrets not being properly declared in your Wrangler under certain circumstances, even if not directly accessed in code. To fix this error, you can run this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npx wranger types<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This generates the types and passes the build. But I&#8217;d recommend just removing the <code>wrangler types --check<\/code> from the build script.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You&#8217;ll absolutely need to run <code>npx wrangler types<\/code> to get typings generated for when you start using Hyperdrive, adding secrets, using Durable Objects, etc. But I wouldn&#8217;t fail the build step if your types aren&#8217;t completely up to date, especially if those mismatches don&#8217;t actually cause TypeScript errors.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If your typings are not correct in a way that matters, you&#8217;ll see TypeScript errors pretty quickly, so the check was never all that valuable to begin with.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And that&#8217;s that. Note that if you got <em>this<\/em> error instead (or ever do get it)<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Just <code>rm -rf node_modules<\/code>, delete your lockfile, then re-run <code>npm i<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Connecting GitHub<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To enable easy deployments, let&#8217;s connect GitHub to our new app. We&#8217;ll go to our <a href=\"https:\/\/dash.cloudflare.com\/\">Cloudflare dashboard<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Find our app under <strong>Workers &amp; Pages<\/strong>.<a href=\"https:\/\/my-blog-staging.adamrackis.workers.dev\/tanstack-cloudflare-sveltekit\/img1.jpg\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"197\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img1.jpg?resize=1024%2C197&#038;ssl=1\" alt=\"\" class=\"wp-image-10547\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img1.jpg?resize=1024%2C197&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img1.jpg?resize=300%2C58&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img1.jpg?resize=768%2C148&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img1.jpg?w=1236&amp;ssl=1 1236w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Go to the <strong>build<\/strong> section.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"155\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2.jpg?resize=1024%2C155&#038;ssl=1\" alt=\"\" class=\"wp-image-10548\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2.jpg?resize=1024%2C155&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2.jpg?resize=300%2C45&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2.jpg?resize=768%2C116&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2.jpg?resize=1536%2C232&amp;ssl=1 1536w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2.jpg?w=1840&amp;ssl=1 1840w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Then choose the right repo.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"488\" height=\"1024\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3.jpg?resize=488%2C1024&#038;ssl=1\" alt=\"\" class=\"wp-image-10549\" style=\"width:271px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3.jpg?resize=488%2C1024&amp;ssl=1 488w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3.jpg?resize=143%2C300&amp;ssl=1 143w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3.jpg?resize=768%2C1612&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3.jpg?resize=732%2C1536&amp;ssl=1 732w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3.jpg?w=784&amp;ssl=1 784w\" sizes=\"auto, (max-width: 488px) 100vw, 488px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Now pushes to <code>main<\/code> will automatically deploy to Cloudflare.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Accessing Cloudflare Goodies from SvelteKit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cloudflare manages the things we need, from secrets to Hyperdrive connection strings on the <code>env<\/code> object. With TanStack we imported our env directly, via a special import.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { env } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudflare:workers\"<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">With SvelteKit, this env object is injected into a <code>platform<\/code> object that shows up in server contexts. In fact, when we first ran <code>npx wranger deploy<\/code> that command, it adjusted our typings for this.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"572\" height=\"552\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img4.jpg?resize=572%2C552&#038;ssl=1\" alt=\"\" class=\"wp-image-10550\" style=\"aspect-ratio:1.0362431575914721;width:310px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img4.jpg?w=572&amp;ssl=1 572w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img4.jpg?resize=300%2C290&amp;ssl=1 300w\" sizes=\"auto, (max-width: 572px) 100vw, 572px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">As we can see, <code>env<\/code> now exists in the platform object. This is what is passed into server loaders like <code>page.server.ts<\/code> for a route.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> type { PageServerLoad } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/$types\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> load: PageServerLoad = <span class=\"hljs-keyword\">async<\/span> ({ platform, locals }) =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">value<\/span>: platform?.env.SECRET_1,\n  };\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Other server-only locations, like API routes, also get <code>env<\/code>. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Note that this does\u00a0<em>not<\/em>\u00a0work for universal loaders, since those also run on the client, and SvelteKit cannot expose things on this Cloudflare\u00a0<code>env<\/code>\u00a0object to the client.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Remote Functions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To access the Cloudflare <code>env<\/code> object from a remote function, you import <code>getRequestEvent<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { getRequestEvent, query } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"$app\/server\"<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Then call it as needed.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getPosts = query(<span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> evt = getRequestEvent();\n  <span class=\"hljs-keyword\">const<\/span> val = evt.platform?.env.SECRET_1;\n\n  <span class=\"hljs-keyword\">return<\/span> &#91;\n    <span class=\"hljs-comment\">\/* ... *\/<\/span>\n  ];\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Databases and Hyperdrive<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Hyperdrive is Cloudflare&#8217;s answer for connecting to a database from a cloud function that can spin up as often as needed, depending on your web application&#8217;s traffic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since Cloudflare workers spin up quickly, on demand, to satisfy the requests they receive, they&#8217;re a poor candidate for opening a fresh TCP connection to your database for each request, since doing so would be slow and would risk overloading your database with more connections than it can support.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We also can&#8217;t just expose a top-level <code>db<\/code> object that&#8217;s exported from a module for reasons we&#8217;ll see shortly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hyperdrive solves all these problems by giving you a pre-warmed connection pool.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Setting up Hyperdrive<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Go to the Cloudflare dashboard and, under Storage and databases, find the option <strong>Postgres &amp; MySQL (Hyperdrive)<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"488\" height=\"508\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img1-1.jpg?resize=488%2C508&#038;ssl=1\" alt=\"\" class=\"wp-image-10552\" style=\"width:353px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img1-1.jpg?w=488&amp;ssl=1 488w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img1-1.jpg?resize=288%2C300&amp;ssl=1 288w\" sizes=\"auto, (max-width: 488px) 100vw, 488px\" \/><figcaption class=\"wp-element-caption\">Amusingly, the Hyperdrive in the menu option may be truncated due to how it&#8217;s displayed.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Hit the <strong>connect to database<\/strong> button.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"105\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2-1.jpg?resize=1024%2C105&#038;ssl=1\" alt=\"\" class=\"wp-image-10553\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2-1.jpg?resize=1024%2C105&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2-1.jpg?resize=300%2C31&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2-1.jpg?resize=768%2C78&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2-1.jpg?resize=1536%2C157&amp;ssl=1 1536w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img2-1.jpg?resize=2048%2C209&amp;ssl=1 2048w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You&#8217;ll be greeted with a few options for how to proceed. For this post, I&#8217;ll be using PlanetScale.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"778\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3a.jpg?resize=1024%2C778&#038;ssl=1\" alt=\"\" class=\"wp-image-10554\" style=\"aspect-ratio:1.3162164937865872;width:646px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3a.jpg?resize=1024%2C778&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3a.jpg?resize=300%2C228&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3a.jpg?resize=768%2C583&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3a.jpg?w=1164&amp;ssl=1 1164w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Follow the prompts, authenticate if needed, select your database, and most importantly, be sure to fill in your database name; you almost certainly do not want the default value of the <code>postgres<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"1019\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3b.jpg?resize=1024%2C1019&#038;ssl=1\" alt=\"\" class=\"wp-image-10555\" style=\"aspect-ratio:1.0049203144296552;width:649px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3b.jpg?resize=1024%2C1019&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3b.jpg?resize=300%2C298&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3b.jpg?resize=150%2C150&amp;ssl=1 150w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3b.jpg?resize=768%2C764&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img3b.jpg?w=1162&amp;ssl=1 1162w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Once complete, you should see a new Wrangler entry.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"1010\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img4-1.jpg?resize=1024%2C1010&#038;ssl=1\" alt=\"\" class=\"wp-image-10556\" style=\"aspect-ratio:1.0138638925696655;width:647px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img4-1.jpg?resize=1024%2C1010&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img4-1.jpg?resize=300%2C296&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img4-1.jpg?resize=768%2C757&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/img4-1.jpg?w=1282&amp;ssl=1 1282w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><figcaption class=\"wp-element-caption\">Screenshot<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s what mine looks like, and no, there&#8217;s nothing secret or private about that data. In fact, you&#8217;ll need it in your Wrangler file and committed to git if you want Cloudflare&#8217;s GitHub integration to work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Copy that into your Wrangler file and add a <code>localConnectionString<\/code> for use during local development.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"hyperdrive\"<\/span>: &#91;\n    {\n      <span class=\"hljs-attr\">\"binding\"<\/span>: <span class=\"hljs-string\">\"HYPERDRIVE\"<\/span>,\n      <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-string\">\"cabc3adcf4c44c03b55e2d17aaef7d99\"<\/span>,\n      <span class=\"hljs-attr\">\"localConnectionString\"<\/span>: <span class=\"hljs-string\">\"postgresql:\/\/docker:docker@localhost:5432\/my_library\"<\/span>\n    }\n  ]\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">You can now update your typings via <code>npx wrangler types<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Connect to Hyperdrive<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now, via your same <code>env<\/code> object, you can connect to your database through Hyperdrive.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> pool = <span class=\"hljs-keyword\">new<\/span> Pool({\n  <span class=\"hljs-attr\">connectionString<\/span>: env.HYPERDRIVE.connectionString,\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Managing Database Connections<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As with TanStack, the same Cloudflare rules apply. We cannot keep a long-running I\/O object open between requests. Doing so would cause errors with Cloudflare; that&#8217;s why we can&#8217;t just <code>export<\/code> a live <code>db<\/code> object from a TypeScript module that contains a database connection when we use Cloudflare Workers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With TanStack Start we solved this with global request middleware, which ran once per request, and allowed us to open a database connection (via Hyperdrive), and put that db object on context, which is present in all server-only contexts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With SvelteKit, we can do similarly with a server hook. If we add a <code>src\/hooks.server.js<\/code> file, the <code>handle<\/code> function exported therefrom is invoked once <em>per request<\/em>, making it a perfect place to set up our database connection.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handle<\/span>(<span class=\"hljs-params\">{ event, resolve }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> pool = <span class=\"hljs-keyword\">new<\/span> Pool({\n    <span class=\"hljs-attr\">connectionString<\/span>: event.platform!.env.HYPERDRIVE.connectionString,\n  });\n  <span class=\"hljs-keyword\">const<\/span> db = getDb(pool);\n\n  <span class=\"hljs-keyword\">if<\/span> (event.url.pathname.includes(<span class=\"hljs-string\">\"\/.well-known\/appspecific\/com.chrome.devtools\"<\/span>)) {\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> Response(<span class=\"hljs-literal\">null<\/span>, { <span class=\"hljs-attr\">status<\/span>: <span class=\"hljs-number\">204<\/span> }); <span class=\"hljs-comment\">\/\/ Return empty response with 204 No Content<\/span>\n  }\n\n  event.locals.db = db;\n\n  <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> resolve(event);\n  <span class=\"hljs-keyword\">return<\/span> response;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">As you can see, we added our <code>db<\/code> object to the <code>event.locals<\/code> object. This is a standard feature with SvelteKit; in fact, there&#8217;s already a <code>Locals<\/code> interface in the <code>src\/app.d.ts<\/code> to hold any of these things we manually add.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-keyword\">type<\/span> { DB } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/data\/db\"<\/span>;\n\n<span class=\"hljs-keyword\">declare<\/span> global {\n  <span class=\"hljs-keyword\">namespace<\/span> App {\n    <span class=\"hljs-keyword\">interface<\/span> Platform {\n      env: Env;\n      ctx: ExecutionContext;\n      caches: CacheStorage;\n      cf?: IncomingRequestCfProperties;\n    }\n\n    <span class=\"hljs-comment\">\/\/ interface Error {}<\/span>\n    <span class=\"hljs-keyword\">interface<\/span> Locals {\n      db: DB;\n    }\n    <span class=\"hljs-comment\">\/\/ interface PageData {}<\/span>\n    <span class=\"hljs-comment\">\/\/ interface PageState {}<\/span>\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">In server-only contexts like Server loaders, we can access our <code>db<\/code> object<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> load: PageServerLoad = <span class=\"hljs-keyword\">async<\/span> ({ platform, locals }) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> users = <span class=\"hljs-keyword\">await<\/span> locals.db.select().from(user).limit(<span class=\"hljs-number\">10<\/span>);\n\n  <span class=\"hljs-keyword\">return<\/span> {\n    users,\n  };\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Similarly in Remote Functions, use the <code>getRequestEvent<\/code> to get the request object, on which you&#8217;ll find the same <code>locals<\/code> object, with the <code>db<\/code> object.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">import<\/span> { eq } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"drizzle-orm\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getRequestEvent, query } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"$app\/server\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { books <span class=\"hljs-keyword\">as<\/span> booksTable } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"$drizzle\/schema\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getBooks = query(<span class=\"hljs-keyword\">async<\/span> () =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> evt = getRequestEvent();\n  <span class=\"hljs-keyword\">const<\/span> books = <span class=\"hljs-keyword\">await<\/span> evt.locals.db.select().from(booksTable).where(eq(booksTable.userId, <span class=\"hljs-string\">\"106394015208813116232\"<\/span>)).limit(<span class=\"hljs-number\">5<\/span>);\n\n  <span class=\"hljs-keyword\">return<\/span> books;\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Concluding Thoughts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;m extremely excited about web development with Cloudflare&#8217;s platform. Workers are an outstanding, low-latency way to host web applications. The SvelteKit integration is great, and with just a few tricks, you can be up and running quickly.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In which Adam highlights the integration process and notes some challenges compared to the TanStack setup we covered shortly ago.<\/p>\n","protected":false},"author":21,"featured_media":10546,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[334,501,386,3,161,162],"class_list":["post-10517","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-cloudflare","tag-cloudflare-workers","tag-database","tag-javascript","tag-svelte","tag-sveltekit"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/cloudflaresveltekit.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10517","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/comments?post=10517"}],"version-history":[{"count":9,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10517\/revisions"}],"predecessor-version":[{"id":10560,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10517\/revisions\/10560"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media\/10546"}],"wp:attachment":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media?parent=10517"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/categories?post=10517"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/tags?post=10517"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}