{"id":10270,"date":"2026-07-02T13:21:36","date_gmt":"2026-07-02T18:21:36","guid":{"rendered":"https:\/\/master.dev\/blog\/?p=10270"},"modified":"2026-07-02T13:21:38","modified_gmt":"2026-07-02T18:21:38","slug":"cloudflare-workers-and-hyperdrive-with-tanstack-start","status":"publish","type":"post","link":"https:\/\/master.dev\/blog\/cloudflare-workers-and-hyperdrive-with-tanstack-start\/","title":{"rendered":"Cloudflare Workers and Hyperdrive with TanStack Start"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Welcome to Part 2 of our series on using Cloudflare for Web Apps (and specifically TanStack Start). <a href=\"https:\/\/master.dev\/blog\/introduction-to-cloudflare-workers-for-web-apps\/\">In Part 1<\/a>, we covered the absolute basics. We deployed a web app to Cloudflare, saw how our Wrangler file works, set up some secrets, and we saw Cloudflare generate typings to keep TypeScript happy.<\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/master.dev\/blog\/introduction-to-cloudflare-workers-for-web-apps\/\">Introduction to Cloudflare Workers\u00a0for Web Apps<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/master.dev\/blog\/cloudflare-workers-and-hyperdrive-with-tanstack-start\/\">Cloudflare Workers and Hyperdrive with TanStack Start<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Now, we&#8217;ll set up a database. We&#8217;ll look at <a href=\"https:\/\/www.cloudflare.com\/products\/hyperdrive\/\">Hyperdrive<\/a> and why it&#8217;s needed, as well as some potentially counterintuitive ways we need to set up our database object (or any I\/O object). We&#8217;ll use TanStack Start specifically here, but these principles apply to any web framework, though some implementation details might differ slightly.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Preliminaries<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I love <a href=\"https:\/\/orm.drizzle.team\/\">Drizzle<\/a> and use it for all my projects. It&#8217;s an outstanding, unique ORM that&#8217;s essentially a thin TypeScript layer on top of SQL. I&#8217;ve written about it&nbsp;<a href=\"https:\/\/master.dev\/blog\/introducing-drizzle\/\">here<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/master.dev\/blog\/drizzle-database-migrations\/\">here<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ll be using Postgres, so we&#8217;ll also install some utilities.<\/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\">npm i drizzle-orm@rc drizzle-kit@rc pg @types\/pg<\/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<p class=\"wp-block-paragraph\">Next, add a <code>drizzle.config.ts<\/code> file.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { defineConfig } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"drizzle-kit\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> connectionString = process.env.POSTGRES!;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> defineConfig({\n  <span class=\"hljs-attr\">dialect<\/span>: <span class=\"hljs-string\">\"postgresql\"<\/span>,\n  <span class=\"hljs-attr\">dbCredentials<\/span>: {\n    <span class=\"hljs-attr\">url<\/span>: connectionString,\n  },\n  <span class=\"hljs-attr\">out<\/span>: <span class=\"hljs-string\">\".\/src\/drizzle\"<\/span>,\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><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 run:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">npx drizzle-kit pull<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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\">That will generate our Drizzle schema. We won&#8217;t cover those specifics here. See the Drizzle posts above if you&#8217;re curious, but really you can query your data however you want; for the purposes of this post, it makes no difference which, if any, ORM you use.<\/p>\n\n\n\n\n  <div class=\"course\">\n          <div class=\"course-image\">\n        <a href=\"https:\/\/master.dev\/courses\/tanstack\/\">          <img decoding=\"async\"\n            src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/tanstack-course.webp?fit=500%2C500&#038;ssl=1\"\n            alt=\"TanStack Start &amp; TanStack Query\"\n          >\n        <\/a>      <\/div>\n    \n    <div class=\"course-text\">\n      \n              <h3 class=\"course-name\">\n                      <a href=\"https:\/\/master.dev\/courses\/tanstack\/\">TanStack Start &amp; TanStack Query<\/a>\n                  <\/h3>\n      \n      \n              <div class=\"course-author\">\n                      <img decoding=\"async\"\n              class=\"course-author-image\"\n              src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/07\/adam.webp?fit=250%2C250&#038;ssl=1\"\n              alt=\"Adam Rackis\"\n            >\n                    <div class=\"course-author-meta\">\n                          <span class=\"course-author-name\">Adam Rackis<\/span>\n                                      <span class=\"course-author-company\">Spotify<\/span>\n                      <\/div>\n        <\/div>\n      \n    <\/div>\n  <\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\">The Wrong Way (for Cloudflare)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For now, let&#8217;s do something fairly common that usually works well enough. We&#8217;ll add a&nbsp;<code>db.ts<\/code>&nbsp;module, with this code<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { drizzle } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"drizzle-orm\/node-postgres\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Pool } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"pg\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> pool = <span class=\"hljs-keyword\">new<\/span> Pool({\n  <span class=\"hljs-attr\">connectionString<\/span>: process.env.POSTGRES!,\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> db = drizzle({ <span class=\"hljs-attr\">client<\/span>: pool });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><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\">Again, this has nothing to do with Drizzle. Export a connection to your database however you&#8217;d like. The issues will be the same.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Issue 1: Performance<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Remember, Cloudflare workers spin up very quickly, on demand, as needed to serve requests. As these (potentially numerous) workers come into existence, each of them establishing a connection to your database poses two problems.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first is performance. Opening a fresh database connection is a relatively slow operation. We don&#8217;t want that happening every time a worker spins up. This is not a concern limited to Cloudflare; any cloud function solution would have the same problem. A web application running atop AWS Lambda would not want to exacerbate existing cold starts by adding TCP database connection overhead, and, of course, low-latency Cloudflare workers would not want to introduce cold-start characteristics in this way.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The second is the sheer&nbsp;<em>number of<\/em>&nbsp;connections that would be stood up in this way. Again, this applies to any platform that works via cloud functions. As your app grows in traffic, the number of cloud functions (Cloudflare workers, AWS Lambda, etc) would grow to a large number, as would the number of connections open on your database. And databases always have a limit on the number of connections they support.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is, of course, a solved problem. Solutions like PgBouncer pool pre-warm connections and act as a proxy to your database. Your application connects to PgBouncer, and PgBouncer provides an open connection. Cloudflare provides its own version of this called Hyperdrive, which we&#8217;ll look at shortly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Issue 2: Per-Request Cleanup<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s this code again:<\/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> { drizzle } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"drizzle-orm\/node-postgres\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Pool } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"pg\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> pool = <span class=\"hljs-keyword\">new<\/span> Pool({\n  <span class=\"hljs-attr\">connectionString<\/span>: process.env.POSTGRES!,\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> db = drizzle({ <span class=\"hljs-attr\">client<\/span>: pool });<\/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\">The second issue with this code is that it violates Cloudflare&#8217;s rule on what is allowed to persist between requests. Long-lived I\/O resources such as Node.js connection pools do not fit the Workers execution model and can trigger runtime errors like this<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"143\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img1-1.jpg?resize=1024%2C143&#038;ssl=1\" alt=\"Cloudflare Error\" class=\"wp-image-10280\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img1-1.jpg?resize=1024%2C143&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img1-1.jpg?resize=300%2C42&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img1-1.jpg?resize=768%2C108&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img1-1.jpg?w=1414&amp;ssl=1 1414w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s solve both of these problems.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hyperdrive<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">No matter&nbsp;<em>how<\/em>&nbsp;we create our database object in code, we don&#8217;t want to connect directly to our source database; we want to connect to a pre-warmed connection pool. Cloudflare provides one for us called Hyperdrive. To get started, go to the Cloudflare dashboard, and under Storage and databases, find the option for &#8220;Postgres &amp; MySQL (Hyperdrive)&#8221;.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter 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\/06\/img2-1.jpg?resize=488%2C508&#038;ssl=1\" alt=\"\" class=\"wp-image-10307\" style=\"width:360px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img2-1.jpg?w=488&amp;ssl=1 488w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img2-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 with how they display it.<\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Hit the connect to database 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\/06\/img3-1.jpg?resize=1024%2C105&#038;ssl=1\" alt=\"\" class=\"wp-image-10310\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3-1.jpg?resize=1024%2C105&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3-1.jpg?resize=300%2C31&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3-1.jpg?resize=768%2C78&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3-1.jpg?resize=1536%2C157&amp;ssl=1 1536w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3-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 <a href=\"https:\/\/planetscale.com\/\">PlanetScale<\/a>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter 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\/06\/img3a.jpg?resize=1024%2C778&#038;ssl=1\" alt=\"\" class=\"wp-image-10311\" style=\"width:588px;height:auto\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3a.jpg?resize=1024%2C778&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3a.jpg?resize=300%2C228&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3a.jpg?resize=768%2C583&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3a.jpg?w=1164&amp;ssl=1 1164w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n<\/div>\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&nbsp;<code>postgres<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><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\/06\/img3b.jpg?resize=1024%2C1019&#038;ssl=1\" alt=\"\" class=\"wp-image-10313\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3b.jpg?resize=1024%2C1019&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3b.jpg?resize=300%2C298&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3b.jpg?resize=150%2C150&amp;ssl=1 150w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img3b.jpg?resize=768%2C764&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/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 be greeted with a new Wrangler entry.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><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\/06\/img4-1.jpg?resize=1024%2C1010&#038;ssl=1\" alt=\"\" class=\"wp-image-10314\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img4-1.jpg?resize=1024%2C1010&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img4-1.jpg?resize=300%2C296&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img4-1.jpg?resize=768%2C757&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img4-1.jpg?w=1282&amp;ssl=1 1282w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/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\u2019ll need it in your Wrangler file and to have committed in Git if you want Cloudflare\u2019s GitHub integration to work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Copy that into your Wrangler file.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"$schema\"<\/span>: <span class=\"hljs-string\">\"node_modules\/wrangler\/config-schema.json\"<\/span>,\n  <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"fitness-tracker\"<\/span>,\n  <span class=\"hljs-attr\">\"compatibility_date\"<\/span>: <span class=\"hljs-string\">\"2025-09-02\"<\/span>,\n  <span class=\"hljs-attr\">\"compatibility_flags\"<\/span>: &#91;<span class=\"hljs-string\">\"nodejs_compat\"<\/span>],\n  <span class=\"hljs-attr\">\"main\"<\/span>: <span class=\"hljs-string\">\"@tanstack\/react-start\/server-entry\"<\/span>,\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\">\"dd0103f82a11410b91c8fb5752050a21\"<\/span>\n    }\n  ],\n  <span class=\"hljs-attr\">\"observability\"<\/span>: {\n    <span class=\"hljs-attr\">\"enabled\"<\/span>: <span class=\"hljs-literal\">true<\/span>\n  },\n  <span class=\"hljs-attr\">\"upload_source_maps\"<\/span>: <span class=\"hljs-literal\">true<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><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\">Now update your typings by running&nbsp;<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\">And now, via your same\u00a0<code>env<\/code>\u00a0object, you can connect to your database through Hyperdrive.<\/p>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-group learn-more\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p class=\"wp-block-paragraph\">If you haven&#8217;t read Part 1 of this, you can grab the <code>env<\/code> object in a TanStack project with this special import\u00a0<\/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> { env } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudflare:workers\"<\/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><\/div><\/div>\n<\/div><\/div>\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\">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-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<p class=\"wp-block-paragraph\">But there&#8217;s one more thing to do. When you attempt to run your app, you&#8217;ll likely see this error<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"50\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img5-1.jpg?resize=1024%2C50&#038;ssl=1\" alt=\"\" class=\"wp-image-10315\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img5-1.jpg?resize=1024%2C50&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img5-1.jpg?resize=300%2C15&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img5-1.jpg?resize=768%2C37&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img5-1.jpg?resize=1536%2C75&amp;ssl=1 1536w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img5-1.jpg?w=1814&amp;ssl=1 1814w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">To fix this, add this connection string to your dev database with that key to your\u00a0<code>.env<\/code>\u00a0file.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE='postgresql:\/\/docker:docker@localhost:5432\/your_db'<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Fixing Per-Request Cleanup<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Our database connections will be much snappier now. But Cloudflare will still error out since our database object is created and exported by a module. That means it will continue to live between requests.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s see how to fix that.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What we need is a fresh database connection&nbsp;<em>per request<\/em>. And it turns out TanStack Start has a feature just for that:&nbsp;<a href=\"https:\/\/tanstack.com\/start\/latest\/docs\/framework\/react\/guide\/middleware#global-middleware\">global request middleware<\/a><\/p>\n\n\n\n<p class=\"learn-more wp-block-paragraph\">Note that I&#8217;m using the word &#8220;connection&#8221; loosely here. We&#8217;re creating a conceptual &#8220;connection&#8221; to our database per request, but we&#8217;re really connecting through Hyperdrive, which pools and reuses actual database TCP connections between requests. The goal is not to literally create a fresh TCP connection to our database for each request; it&#8217;s to avoid long-lived I\/O resources between requests.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The focus of this post is Cloudflare, so we&#8217;ll breeze through the code; check the docs for more info.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\">&lt;em&gt;<span class=\"hljs-comment\">\/\/ src\/start.ts&lt;\/em&gt;<\/span>\n<span class=\"hljs-keyword\">import<\/span> { Pool } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"pg\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { createCsrfMiddleware, createMiddleware, createStart } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@tanstack\/react-start\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getDb } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/data\/db\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> globalContextMiddleware = createMiddleware().server(<span class=\"hljs-keyword\">async<\/span> ({ next }) =&gt; {\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> pool = <span class=\"hljs-keyword\">new<\/span> Pool({\n      connectionString: process.env.POSTGRES!,\n    });\n\n    <span class=\"hljs-keyword\">const<\/span> db = getDb(pool);\n\n    <span class=\"hljs-keyword\">return<\/span> next({\n      context: {\n        db,\n      },\n    });\n  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n    <span class=\"hljs-built_in\">console<\/span>.log({ msg: <span class=\"hljs-string\">\"Error in root context middleware\"<\/span>, error });\n    <span class=\"hljs-keyword\">throw<\/span> error;\n  }\n});\n\n<span class=\"hljs-keyword\">const<\/span> csrfMiddleware = createCsrfMiddleware({\n  filter: <span class=\"hljs-function\"><span class=\"hljs-params\">ctx<\/span> =&gt;<\/span> ctx.handlerType === <span class=\"hljs-string\">\"serverFn\"<\/span>,\n});\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> startInstance = createStart(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> ({\n  requestMiddleware: &#91;csrfMiddleware, globalContextMiddleware],\n  functionMiddleware: &#91;],\n}));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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\">This middleware will run once&nbsp;<em>per request<\/em>, which is exactly what we want. We create our database object and then add it to the context.<\/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\">return<\/span> next({\n  <span class=\"hljs-attr\">context<\/span>: {\n    db,\n  },\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<p class=\"wp-block-paragraph\">Now you can access the&nbsp;<code>db<\/code>&nbsp;object from any server functions, or server routes (API routes) via the&nbsp;<code>context<\/code>&nbsp;object that&#8217;s passed in.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"154\" src=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img6-1.jpg?resize=1024%2C154&#038;ssl=1\" alt=\"\" class=\"wp-image-10316\" srcset=\"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img6-1.jpg?resize=1024%2C154&amp;ssl=1 1024w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img6-1.jpg?resize=300%2C45&amp;ssl=1 300w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img6-1.jpg?resize=768%2C116&amp;ssl=1 768w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img6-1.jpg?resize=1536%2C231&amp;ssl=1 1536w, https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/img6-1.jpg?w=1728&amp;ssl=1 1728w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Odds and Ends<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re connecting to a database that&#8217;s hosted in a particular region, you&#8217;ll almost always want your web app served from the same region. Putting the workers serving your app closer to your users might seem appealing, but that only serves to make the workers further from your database, increasing the latency of your queries and updates, and your app will likely need to make&nbsp;<em>multiple<\/em>&nbsp;requests to your database in the process of serving a request.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">My PlanetScale database is in AWS&#8217;s us-east-1 region, and so I can pin my Cloudflare app to the same region with this entry in my Wrangler file.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\"><span class=\"hljs-string\">\"placement\"<\/span>: {\n  <span class=\"hljs-attr\">\"region\"<\/span>: <span class=\"hljs-string\">\"aws:us-east-1\"<\/span>,\n},<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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\">It can make a large difference. I saw the latency in running a very simple query against a small table explode from about 7ms when placed in the same region as my database, up to over 10X (about 80ms) when placed on the United States West Coast.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Concluding Thoughts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I absolutely love Cloudflare&#8217;s development platform. Workers are an outstanding, low-latency way to host your web application. I hope this post has provided the tools you need to get your first app up and running there.<\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/master.dev\/blog\/introduction-to-cloudflare-workers-for-web-apps\/\">Introduction to Cloudflare Workers\u00a0for Web Apps<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/master.dev\/blog\/cloudflare-workers-and-hyperdrive-with-tanstack-start\/\">Cloudflare Workers and Hyperdrive with TanStack Start<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>In Part 2 of the series on using Cloudflare with Web Apps, the focus is on setting up a database and addressing key issues like performance and connection management.<\/p>\n","protected":false},"author":21,"featured_media":10206,"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,507,240],"class_list":["post-10270","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-cloudflare","tag-cloudflare-workers","tag-hyperdrive","tag-tanstack"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/tanstack-workers-1.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10270","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=10270"}],"version-history":[{"count":15,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10270\/revisions"}],"predecessor-version":[{"id":10329,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/10270\/revisions\/10329"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media\/10206"}],"wp:attachment":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media?parent=10270"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/categories?post=10270"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/tags?post=10270"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}