<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: ambergristle</title>
    <description>The latest articles on Forem by ambergristle (@ambergristle).</description>
    <link>https://forem.com/ambergristle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2519659%2Ff5f39c97-3f69-4224-8fc8-e0810cf102d5.png</url>
      <title>Forem: ambergristle</title>
      <link>https://forem.com/ambergristle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ambergristle"/>
    <language>en</language>
    <item>
      <title>Rate Limiting Hono Apps: An Introduction</title>
      <dc:creator>ambergristle</dc:creator>
      <pubDate>Thu, 22 May 2025 04:57:38 +0000</pubDate>
      <link>https://forem.com/fiberplane/an-introduction-to-rate-limiting-3j0</link>
      <guid>https://forem.com/fiberplane/an-introduction-to-rate-limiting-3j0</guid>
      <description>&lt;p&gt;Rate limiting is essential for most production applications. At a minimum, it prevents floods of traffic from crashing services, and it mitigates an app’s vulnerability to a variety of attacks. Adding and effectively configuring rate limiting is no simple task though. There are many rate limiting tools and strategies to choose from, and it’s difficult to find resources that concretely explain how to use rate limiting to improve an app’s security and resilience.&lt;/p&gt;

&lt;p&gt;This is the first article in a series that will bring together the theory and practice of rate limiting, using contextualized real-world examples. In each article, I’ll demonstrate how to add different rate limiters to a &lt;a href="https://hono.dev/docs/" rel="noopener noreferrer"&gt;Hono&lt;/a&gt; app, and discuss the technical and business requirements they fulfill.&lt;/p&gt;

&lt;p&gt;I’ll begin with &lt;a href="https://github.com/rhinobase/hono-rate-limiter" rel="noopener noreferrer"&gt;&lt;code&gt;hono-rate-limiter&lt;/code&gt;&lt;/a&gt;, a low-lift solution that addresses many common rate limiting needs. While fairly simple to use, it will help illustrate key rate limiting concepts that will remain relevant throughout the series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Appropriately identifying clients&lt;/li&gt;
&lt;li&gt;Configuring the allowed request rate&lt;/li&gt;
&lt;li&gt;Performantly tracking client requests&lt;/li&gt;
&lt;li&gt;Communicating limits and handling rejected requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next article, I’ll do a deeper dive into rate limiting algorithms and design, using the &lt;a href="https://github.com/upstash/ratelimit-js/tree/main" rel="noopener noreferrer"&gt;Upstash rate limiter&lt;/a&gt; to create custom rate limiting middleware. Finally, I’ll walk through how to roll your own rate limiting solution, shifting focus from high-level strategies and patterns to more specific implementation concerns.&lt;/p&gt;

&lt;p&gt;First though, I’ll briefly introduce the “whats” and “whys” of rate limiting to provide some context for the following discussion of &lt;code&gt;hono-rate-limiter&lt;/code&gt; implementations. This article won’t be especially technical, but it will help to have a basic familiarity with Hono apps, as well as the business and engineering requirements that affect backend development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is rate limiting anyway?
&lt;/h2&gt;

&lt;p&gt;Simply put, rate limiters control how often a resource can be accessed, and by whom. To accomplish this, most limiters use some kind of identifier (e.g., user ID or client IP) to track requests from a given source, and an algorithm to determine if and when requests from each source should be allowed. &lt;/p&gt;

&lt;p&gt;The specifics vary by algorithm and implementation, but most limiters are defined by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;limit&lt;/strong&gt; on how many requests can be made within a defined &lt;strong&gt;window&lt;/strong&gt; of time&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;cost&lt;/strong&gt; of each request counted against the limit&lt;/li&gt;
&lt;li&gt;How often a client’s limit is &lt;strong&gt;refilled&lt;/strong&gt; or &lt;strong&gt;reset&lt;/strong&gt;, allowing additional requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Client consumption is measured in &lt;strong&gt;tokens&lt;/strong&gt; (or as the sum of request timestamps), typically persisted in some kind of database. Each time the client makes a request, the rate limiting algorithm is used to determine whether the number of recent requests exceeds the limit. Excessive requests are usually rejected, with a &lt;code&gt;Retry-After&lt;/code&gt; header specifying when the client can try again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why rate limit at all?
&lt;/h2&gt;

&lt;p&gt;Rate limiting refers to a broad variety of policies and implementations with equally-diverse goals. It is most commonly deployed to manage service usage and capacity, but it also plays a vital role in auth workflows. Naturally, where and how this limiting is implemented depends greatly on the abuse vectors it guards against.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource Exhaustion
&lt;/h3&gt;

&lt;p&gt;An app’s ability to process requests is essentially finite. If too many requests come in at once, an unprotected system could crash, or auto-scale to the tune of tens of thousands of dollars.&lt;/p&gt;

&lt;p&gt;Floods of traffic may reflect an innocent spike in engagement—thousands of users flocking to buy tickets—or a &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html" rel="noopener noreferrer"&gt;Denial of Service (DoS) attack&lt;/a&gt; intended to block legitimate requests or bring down the server. In either case, rate limiting is used to cap processed requests at a manageable (and affordable) level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource Exploitation
&lt;/h3&gt;

&lt;p&gt;Even if an app can process all the traffic it’s receiving, it may not be serving clients equally or fairly. Especially in the case of paid services—where users expect a defined level of access—it’s vital to constrain how often clients can access resources.&lt;/p&gt;

&lt;p&gt;Without account-based rate limits, a minority of users can hog the app’s capacity—e.g., through high-volume scraping, causing other users’ requests to lag or fail. Users can also exceed the consumption rate they’re paying for, leading to a “hidden” loss of revenue.&lt;/p&gt;

&lt;p&gt;Note that “capacity” doesn’t refer only to how many requests a service can handle. Both malicious and innocent request patterns can over-consume other app resources, like the availability of merchandise on a digital marketplace (aka &lt;a href="https://owasp.org/www-project-automated-threats-to-web-applications/assets/oats/EN/OAT-021_Denial_of_Inventory" rel="noopener noreferrer"&gt;Inventory Denial&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Account Hijacking
&lt;/h3&gt;

&lt;p&gt;Not all vulnerabilities are a matter of capacity. Rate limiting in auth workflows is primarily deployed against &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html#take-precautions-against-brute-forcing" rel="noopener noreferrer"&gt;Brute Force&lt;/a&gt; and &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.html#introduction" rel="noopener noreferrer"&gt;Credential Stuffing&lt;/a&gt; attacks. Brute force attacks attempt to gain access to a system by repeatedly guessing a user’s password, while credential stuffing blindly plugs known credentials into different services, seeking instances of their reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hono Rate Limiter
&lt;/h2&gt;

&lt;p&gt;The easiest way to mitigate these risks in Hono apps is with &lt;code&gt;hono-rate-limiter&lt;/code&gt;. Built to satisfy basic rate limiting needs in the Hono ecosystem, &lt;code&gt;hono-rate-limiter&lt;/code&gt; is a port of the popular &lt;code&gt;express-rate-limiter&lt;/code&gt;. While much of the API and core logic are the same, the library leverages Hono’s type system to share limiter results with downstream logic. It also includes Cloudflare-specific tooling to support development with the &lt;code&gt;workerd&lt;/code&gt; runtime.&lt;/p&gt;

&lt;p&gt;Like many Hono tools, &lt;code&gt;hono-rate-limiter&lt;/code&gt; exports middleware that can be added app-wide, or to a single route or handler. This flexibility is especially important in the context of rate limiting, where multiple limiters may be layered to address different requirements. A data endpoint, for example, might need one rate limit to prevent resource exhaustion and another to enforce account-specific limits by subscription tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Window + Limit
&lt;/h2&gt;

&lt;p&gt;As their name suggests, rate limiters constrain the number of requests allowed within a given period. This is typically expressed as a &lt;code&gt;max&lt;/code&gt; or &lt;code&gt;limit&lt;/code&gt; of requests, paired with a time interval that represents how often a client’s available requests are either reset or refilled. These are the primary levers used to configure and tune a rate limiter’s behavior, and each rate limiting algorithm leverages them differently to balance performance and accuracy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calculating client request rates
&lt;/h3&gt;

&lt;p&gt;Fixed Window algorithms—like the one used by &lt;code&gt;hono-rate-limiter&lt;/code&gt;, divide time into fixed-length windows (measured in milliseconds), and maintain a counter tracking the number of requests made in each. Setting the &lt;code&gt;limit&lt;/code&gt; to 100 and &lt;code&gt;windowMs&lt;/code&gt; to 60k, for example, allows 100 requests every minute. Requests that exceed the window limit are rejected until &lt;code&gt;windowMs&lt;/code&gt; has elapsed, at which point the counter is reset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rateLimiter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono-rate-limiter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fixed Window limitations
&lt;/h3&gt;

&lt;p&gt;The Fixed Window algorithm is the simplest and most performant, but it has a few key drawbacks. Since it uses fixed windows in time, the algorithm is too rigid to support occasional traffic bursts, even when they’re within the allowed long-term average.&lt;/p&gt;

&lt;p&gt;It is also vulnerable to abuse at the window transition. By maxing out requests just before the current window expires, and just after the next begins, malicious clients can momentarily double the limit for a burst of requests.&lt;/p&gt;

&lt;p&gt;In the next article, I’ll introduce more flexible and accurate options. Their increased efficacy comes at the cost of logical complexity and performance though, so for many use-cases the Fixed Window algorithm’s strengths outweigh its weaknesses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calibrating rate limits
&lt;/h3&gt;

&lt;p&gt;The appropriate configuration for a rate limiter depends largely on the problem it’s meant to solve. Each use-case has distinct priorities and constraints, and effective rate limits are the product of balancing one against the other.&lt;/p&gt;

&lt;p&gt;Limiters enforcing tiered API access balance resource costs against subscription prices, while those protecting auth routes prioritize security while allowing for some user error. Likewise, limiters preventing resource exhaustion should reflect average consumption relative to capacity.&lt;/p&gt;

&lt;p&gt;When first adding a rate limiter, it’s best to choose a more conservative limit in order to ensure that all users have fair access and that service isn’t disrupted. This initial configuration can then be tuned over time to better reflect actual request patterns.&lt;/p&gt;

&lt;p&gt;Sophisticated rate limiters may use dynamic limits to account for fluctuations throughout the day, or to handle traffic spikes related to planned events like a promotion or content release. In most cases though, using server metrics to regularly tune static rate limits is good enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uniquely identifying clients
&lt;/h2&gt;

&lt;p&gt;Both the standard and Cloudflare-specific middleware require a &lt;code&gt;keyGenerator&lt;/code&gt;, which takes Hono &lt;code&gt;Context&lt;/code&gt; as an argument and must return a string key. This key is used to distinguish between records in the limiter store, so for most use-cases it should be unique to each client.&lt;/p&gt;

&lt;p&gt;While many rate limiting examples use the client IP for convenience, this isn’t recommended for production apps. IP addresses aren’t guaranteed to be unique, and bad actors can easily spoof IPs or obscure them with a VPN. &lt;/p&gt;

&lt;p&gt;A user or account ID is the best option for most use-cases, since their uniqueness makes it easier to apply rate limits fairly and accurately. This is essential for premium APIs and other subscription services, which promise customers a defined level of access for each payment tier.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to rate limit by IP
&lt;/h3&gt;

&lt;p&gt;Rate limiters protecting pre-login or unprotected routes are a notable exception. Uniquely identifying clients is more difficult in these cases, but also less relevant to the limiters’ role, which is focused on security and capacity management more-so than regulating individual client usage.&lt;/p&gt;

&lt;p&gt;In auth workflows, rate limiters make attempts to gain unauthorized access to the system logistically challenging or prohibitively expensive. Since users only need to submit their credentials once, any IPs that exceed a reasonable rate limit are suspicious, and may even be blocked after repeated violations.&lt;/p&gt;

&lt;p&gt;Managing service capacity is a more generalized requirement, but is likewise decoupled from individual client usage. Rate limiting can be used to prevent apps from over-consuming downstream APIs, including third-party services. It can also ensure that resource use doesn’t exceed server capacity, or unexpectedly trigger expensive auto-scaling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safeguarding user privacy
&lt;/h3&gt;

&lt;p&gt;Since rate limiting databases maintain a record of API usage by key, it’s also vital to consider user privacy when limiting by IP, or other personally-identifiable data. This also includes identifiers like geolocation and device fingerprints, which will be covered in greater detail later in the series.&lt;/p&gt;

&lt;p&gt;Storing personally-identifiable data makes users vulnerable to doxxing, targeted cyber attacks, and government overreach. It can also make apps liable to laws like the EU’s GDPR or California’s CCPA, which are designed to protect users by regulating the collection and storage of personal data. Fortunately, there are a few simple steps we can take to protect users and their privacy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When collecting or storing identifying data is necessary, clearly communicate to users what data is used and why.&lt;/li&gt;
&lt;li&gt;Obscure identifying data like IPs with one-way hashes (e.g., &lt;code&gt;HMAC&lt;/code&gt;), and regularly clear stale records from the limiter database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As with every engineering problem, ethical rate limiting is a matter of compromise. We can’t safeguard both the application and its users without dealing with at least some identifying data. This shouldn’t be seen as an excuse to cut corners though, but rather as a challenge to better understand rate limiting tools and how to deploy them responsibly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating keys from request data
&lt;/h3&gt;

&lt;p&gt;Rate limiting on protected API routes should take place downstream from auth middleware. This avoids wasting resources limiting an unauthorized request, and ensures that the user or account ID is available when &lt;code&gt;hono-rate-limiter&lt;/code&gt; is called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rateLimiter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono-rate-limiter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppEnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;rateLimiter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppEnv&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
      &lt;span class="na"&gt;keyGenerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accessing the ID type-safely in the &lt;code&gt;keyGenerator&lt;/code&gt; is a two-step process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, the ID must be &lt;a href="https://hono.dev/docs/api/context#set-get" rel="noopener noreferrer"&gt;&lt;code&gt;set&lt;/code&gt; in &lt;code&gt;Context&lt;/code&gt;&lt;/a&gt;. Some Hono auth middleware do this internally, but others—like the &lt;a href="https://hono.dev/docs/middleware/builtin/bearer-auth" rel="noopener noreferrer"&gt;built-in Bearer Auth&lt;/a&gt;—do not. In these cases, an additional layer of custom middleware is necessary.&lt;/li&gt;
&lt;li&gt;Then, the variable must be added to a custom &lt;code&gt;AppEnv&lt;/code&gt; type, passed to the &lt;code&gt;rateLimiter&lt;/code&gt; middleware as a generic parameter. This type will be applied to the &lt;code&gt;keyGenerator&lt;/code&gt;'s &lt;code&gt;Context&lt;/code&gt; argument, making the value available via &lt;code&gt;c.var&lt;/code&gt; or &lt;code&gt;c.get&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that this approach is truly type-safe only when the &lt;code&gt;set&lt;/code&gt; invocation and &lt;code&gt;AppEnv&lt;/code&gt; type are coupled. While this isn’t entirely possible due to Hono’s optimistic approach to type-safety, using &lt;a href="https://hono.dev/docs/helpers/factory" rel="noopener noreferrer"&gt;factory helpers&lt;/a&gt; like &lt;code&gt;createHandlers&lt;/code&gt; or &lt;code&gt;createApp&lt;/code&gt; can help keep types in sync with the implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persisting request records
&lt;/h2&gt;

&lt;p&gt;To keep track of client requests, rate limiters rely on some kind of mid-term persistence. Records don’t need to be maintained forever, but a stateless rate limiter would have no way to know how often a client was hitting the backend, so it would have to naively allow (or reject) every request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not use local memory?
&lt;/h3&gt;

&lt;p&gt;Many rate limiting examples or bare-bones implementations simply track client requests locally, often using a &lt;code&gt;Map&lt;/code&gt; stored in memory. While this can be helpful during development, or for small apps running in a persisted environment, it isn’t a good solution at scale.&lt;/p&gt;

&lt;p&gt;Local memory implementations break down in edge environments—where memory isn’t persisted long-term, and in distributed systems—where multiple app instances must be kept in sync. A local solution also means that the limiter and application logic share memory and compute, making scaling more difficult.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting to remote databases
&lt;/h3&gt;

&lt;p&gt;Remote in-memory databases are the most common solution, as they minimize latency in the rate limit layer. Since some algorithms require multiple database calls, atomicity is another important consideration when choosing a storage solution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Atomic operations are completed as a single unit, even if they have multiple steps. This prevents race conditions that could result in conflicting limits results. I’ll discuss this topic more in the next article on rate limiting algorithms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://redis.io/docs" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; is a popular choice, both because it is extremely performant, and because it makes it easy to run multi-step operations atomically. Other in-memory databases like Memcached or Cloudflare KV can also be used, though they are geared towards caching and don’t offer robust support for atomic operations.&lt;/p&gt;

&lt;p&gt;Application architecture is arguably the most important factor though. Modern development patterns like distributed applications and edge functions introduce challenges and constraints that affect not just where data is stored, but also how it’s managed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distributed limiting
&lt;/h3&gt;

&lt;p&gt;While a centralized store makes rate limit records available across requests and app instances, it introduces a request-processing bottleneck. A flood of requests from instances around the world can bog down the database, causing even legitimate requests to lag or time-out.&lt;/p&gt;

&lt;p&gt;In distributed applications, centralized stores can also introduce significant latency. After reaching the handler, each request must first be diverted to the limiter—possibly in a different region—before returning to the handling server for processing.&lt;/p&gt;

&lt;p&gt;In the era of edge functions (and runtimes), it’s no surprise that distributed rate limiters are increasingly popular. This strategy involves colocating a limiter data store with each app instance, thereby substantially reducing latency and avoiding a single point of failure.&lt;/p&gt;

&lt;p&gt;Of course, distributed rate limiters bring their own set of challenges. As with any distributed database, updates must be synchronized across instances. This ensures that users can’t bypass the limit simply by changing their “location” with a VPN. Special care must also be taken to mitigate race conditions, especially when using non-unique identifiers like IPs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data storage with &lt;code&gt;hono-rate-limiter&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;At the heart of &lt;code&gt;hono-rate-limiter&lt;/code&gt; are its stores. These are adapters that implement the Fixed Window algorithm and communicate with the persistence layer. The library includes a Redis store compatible with a most Redis clients, as well as stores for Cloudflare &lt;a href="https://developers.cloudflare.com/kv/" rel="noopener noreferrer"&gt;KV databases&lt;/a&gt; and &lt;a href="https://developers.cloudflare.com/durable-objects/" rel="noopener noreferrer"&gt;Durable Objects&lt;/a&gt;. It can also be paired with a &lt;a href="https://github.com/@upstash/redis" rel="noopener noreferrer"&gt;half-dozen third-party stores&lt;/a&gt;, and developers can build their own to connect with a different database or use an alternative algorithm.&lt;/p&gt;

&lt;p&gt;Setting up a store is pretty straightforward, though implementations and configuration options will depend on the store and database selected. In the simplest case, the initialized database client is passed to the store constructor. For more information on specific integrations, please refer to the &lt;a href="https://github.com/rhinobase/hono-rate-limiter" rel="noopener noreferrer"&gt;&lt;code&gt;hono-rate-limiter&lt;/code&gt; documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RedisStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hono-rate-limiter/redis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@upstash/redis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// OR import { Redis } from "@upstash/redis/cloudflare";&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;UPSTASH_REDIS_REST_URL&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;UPSTASH_REDIS_REST_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RedisStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that database options aren’t necessarily compatible with every architecture. Since the Cloudflare Workers runtime doesn’t support TCP connections, connecting with standard Redis databases would require a custom HTTP client. To use Redis with a Worker, an edge-compatible implementation like &lt;a href="https://github.com/upstash/redis-js" rel="noopener noreferrer"&gt;Upstash Redis&lt;/a&gt; is recommended. Connectivity issues aside, this will also make it easier to keep database instances (and rate limits) in sync. &lt;/p&gt;

&lt;h3&gt;
  
  
  Using the Cloudflare Rate Limit binding
&lt;/h3&gt;

&lt;p&gt;An adapter for Cloudflare’s &lt;a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/" rel="noopener noreferrer"&gt;Rate Limit binding&lt;/a&gt; is also available as a standalone middleware. In this case, a store isn’t required, since the binding handles the algorithm and database connection internally. This also results in some important configuration and implementation differences.&lt;/p&gt;

&lt;p&gt;The Cloudflare Rate Limit binding requires a window of either 10 or 60 seconds, restricting configurability. Limits are local to each location the Worker runs in, so a client could bypass limits by directing requests to different Worker instances.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cloudflareRateLimiter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hono-rate-limiter/cloudflare&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Bindings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// `RateLimit` type available globally through @cloudflare/workers-types&lt;/span&gt;
    &lt;span class="na"&gt;RATE_LIMITER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RateLimit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;cloudflareRateLimiter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;rateLimitBinding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RATE_LIMITER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyGenerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generateClientKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the Cloudflare Rate Limit API doesn’t require a &lt;code&gt;hono-rate-limiter&lt;/code&gt; store, it’s unclear which rate limit algorithm it uses. Based on &lt;a href="https://blog.cloudflare.com/counting-things-a-lot-of-different-things/" rel="noopener noreferrer"&gt;this (somewhat dated) article&lt;/a&gt; though, it’s seems safe to assume it’s a Sliding Window Counter. In the next article, I'll cover the differences between it and the Fixed Window in detail. For now it's enough to know that the Sliding Window takes the same arguments but offers better accuracy and burst support, making it a better candidate for an enterprise product.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation differences
&lt;/h3&gt;

&lt;p&gt;Though the stores use the same algorithm, there is at least one significant difference in their implementation. When using &lt;code&gt;hono-rate-limiter&lt;/code&gt; with Redis, the &lt;code&gt;windowMs&lt;/code&gt; option is used to automatically delete windows when they expire, improving limiter performance. This behavior isn’t available when storing rate limit data with Cloudflare products like KV Stores or the Rate Limit binding though. Instead, the rate limiting clients exported by &lt;code&gt;@hono-rate-limiter/cloudflare&lt;/code&gt; check whether the tracked window has expired on each request, and manually reset the counter when relevant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Communicating rate limits
&lt;/h2&gt;

&lt;p&gt;To improve user experience—and to help clients avoid accidentally hitting rate limits—it’s helpful to share rate limit and usage data with clients. In the simplest case, servers return a &lt;code&gt;429&lt;/code&gt; error when a rate limit is exceeded. This isn’t especially helpful though, regardless of whether the client is an internal front-end, or an end-user. For this reason, both success and error responses often include rate limit information in their headers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rate limiters protecting auth logic from attacks are a notable exception. In these cases it is recommended to share as little as possible. Any details about the limit or when it resets make it easier to bypass limits, automate attacks, or avoid detection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Rate limit headers
&lt;/h3&gt;

&lt;p&gt;Though the standard format for rate limit headers has changed over time, the information shared has remained largely consistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Policy:&lt;/strong&gt; The service’s request limit and window length in seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit:&lt;/strong&gt; How many requests the client is allowed within a given window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remaining:&lt;/strong&gt; The number of allowed requests remaining (&lt;code&gt;limit - consumed&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reset:&lt;/strong&gt; When the limit will be reset, in seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By default, &lt;code&gt;hono-rate-limiter&lt;/code&gt; uses the &lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-06" rel="noopener noreferrer"&gt;Draft 6&lt;/a&gt; standard, setting each value in its own &lt;code&gt;RateLimit-*&lt;/code&gt; header, and including only non-zero Reset values. It also supports the &lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-07" rel="noopener noreferrer"&gt;Draft 7 standard&lt;/a&gt;, which combines all fields except for Policy into a single &lt;code&gt;RateLimit&lt;/code&gt; header, and always includes a &lt;code&gt;Reset&lt;/code&gt; value. In both cases, a &lt;code&gt;Retry-After&lt;/code&gt; header is set on error responses to let clients know when they can try again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rateLimiter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono-rate-limiter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
      &lt;span class="na"&gt;standardHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft-7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or "draft-6"&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’m not aware of any technical motivations for using one or the other, though Draft 7 may be preferable as it’s the most recent (available) standard. Since &lt;code&gt;hono-rate-limiter&lt;/code&gt;'s implementation of the two standards is essentially the same, the choice may come down to personal preference for the ergonomics of one or the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing rate limiters for specific use-cases
&lt;/h2&gt;

&lt;p&gt;Adding rate limiting to a Hono app with &lt;code&gt;hono-rate-limiter&lt;/code&gt; is pretty simple, without incurring vendor lock-in or conforming to a prescriptive API. While it may not be sufficient for all use-cases, it’s a solid and easily-extensible starting point. Its greatest limitation may be its use of the Fixed Window algorithm, but this can be addressed with a custom store.&lt;/p&gt;

&lt;p&gt;I hope that this has been a helpful introduction to rate limiting in the Hono ecosystem. In the next article I’ll cover rate limiting algorithms in greater depth, addressing their trade-offs and explain how to extend &lt;code&gt;hono-rate-limiter&lt;/code&gt; with a custom store. If there’s something I’ve missed, or that you’d like me to cover in greater depth, please leave a comment below!&lt;/p&gt;

</description>
      <category>hono</category>
      <category>ratelimit</category>
      <category>architecture</category>
      <category>learning</category>
    </item>
    <item>
      <title>Seeding and deploying HONC apps</title>
      <dc:creator>ambergristle</dc:creator>
      <pubDate>Mon, 24 Mar 2025 16:14:07 +0000</pubDate>
      <link>https://forem.com/fiberplane/placegoose-seeding-and-deployment-with-honc-5f88</link>
      <guid>https://forem.com/fiberplane/placegoose-seeding-and-deployment-with-honc-5f88</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/fiberplane/placegoose-building-data-apis-with-honc-id8"&gt;first article of this series&lt;/a&gt;, we walked through building Placegoose, a simple mock data API using the &lt;a href="https://honc.dev/" rel="noopener noreferrer"&gt;HONC stack&lt;/a&gt;. My goals were pretty simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Showcase solutions for common &lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;Hono&lt;/a&gt; project requirements, like relations and rate limiting&lt;/li&gt;
&lt;li&gt;Provide a free mock data API that returns error responses for invalid requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s easy enough to find Hono starter templates with a “Hello World” endpoint, or a basic configuration, but there are fewer resources out there that demonstrate how to integrate non-trivial services and functionality. In the same vein, many popular mock data APIs don’t validate requests or implement rate-limiting, which makes it impossible to test sad paths.&lt;/p&gt;

&lt;p&gt;Placegoose was meant to be a step up in complexity from the average starter template, without becoming overly-specific. While you’re welcome to &lt;a href="https://placegoose.fp.dev/" rel="noopener noreferrer"&gt;test out the API&lt;/a&gt;, you may find it more useful to &lt;a href="https://github.com/fiberplane/create-honc-app/tree/main/examples/placegoose" rel="noopener noreferrer"&gt;clone the project&lt;/a&gt; and adjust the database schema and mock data to fit your needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does production deployment entail?
&lt;/h3&gt;

&lt;p&gt;In this article, we’ll be covering the steps to prepare apps like Placegoose for Cloudflare deployment. Placegoose is a fairly simple data API with statically-served docs, so we won’t be addressing monorepos or use-cases that require SSR or CSR front-ends.&lt;/p&gt;

&lt;p&gt;I won’t be getting too technical, but you’ll need to be comfortable with web fundamentals and reading TypeScript, as I won’t be explaining implementation details. It will also help if you’re familiar with Drizzle configuration and database schemas. If you’re new to the HONC stack, I’d recommend giving &lt;a href="https://dev.to/fiberplane/placegoose-building-data-apis-with-honc-id8"&gt;the previous article&lt;/a&gt; a read first!&lt;/p&gt;

&lt;p&gt;While many production deployments require a complex multi-stage build process that incorporates advanced features like testing or dependency optimization,  we’ll focus on two key steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating and seeding a remote D1 database&lt;/li&gt;
&lt;li&gt;Deploying a Workers project using the &lt;a href="https://developers.cloudflare.com/workers/wrangler/commands/" rel="noopener noreferrer"&gt;Cloudflare CLI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CLI makes deploying simple apps fairly trivial, but seeding the remote D1 proved to be a little more complicated than anticipated. In this article, we’ll discuss some of the infrastructure constraints you may encounter, and the Cloudflare and Drizzle tooling used to work around them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seeding a remote D1 over HTTP using &lt;a href="https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit" rel="noopener noreferrer"&gt;Drizzle’s generic HTTP adapter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Or using &lt;a href="https://developers.cloudflare.com/d1/wrangler-commands" rel="noopener noreferrer"&gt;Cloudflare’s CLI&lt;/a&gt; to apply a locally-generated SQL file to a remote D1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both solutions rely on locally-run scripts that use pre-generated data. With some adjustments, though, it should be possible to integrate them into a &lt;a href="https://developers.cloudflare.com/workers/wrangler/custom-builds/" rel="noopener noreferrer"&gt;custom build&lt;/a&gt; process. Doing so was out of scope for Placegoose, but might be relevant if you regularly spin up demo instances for new clients.&lt;/p&gt;

&lt;p&gt;If you have a better solution, or think you see a problem with one of the implementations, please let me know in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting set up with Cloudflare
&lt;/h2&gt;

&lt;p&gt;When deploying Workers that rely on bindings to other Cloudflare services, we must first ensure that these services are live. Otherwise, we’ll get an error like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;binding DB of &lt;span class="nb"&gt;type &lt;/span&gt;d1 must have a database that already exists. Use wrangler or the UI to create the database. &lt;span class="o"&gt;[&lt;/span&gt;code: 10021]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating Cloudflare services is simple enough. First, &lt;a href="https://dash.cloudflare.com/sign-up" rel="noopener noreferrer"&gt;create a Cloudflare account&lt;/a&gt;, if you haven’t already, then run the &lt;code&gt;d1 create&lt;/code&gt; command to create a new remote D1 database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler d1 create placegoose-d1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the database has been created, the script will log the binding details. You can also find these values in &lt;a href="https://dash.cloudflare.com" rel="noopener noreferrer"&gt;your Cloudflare dashboard&lt;/a&gt;. Be sure to update your &lt;code&gt;wrangler.toml&lt;/code&gt; file with the logged &lt;code&gt;database_id&lt;/code&gt;. You’ll also need to add your account ID, database ID, and Cloudflare API token to your &lt;code&gt;prod.vars&lt;/code&gt; file in order to run local migration and seeding scripts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[d1_databases]]&lt;/span&gt;
&lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DB"&lt;/span&gt;
&lt;span class="py"&gt;database_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"placegoose-d1"&lt;/span&gt;
&lt;span class="py"&gt;database_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;DATABASE-ID-FROM-ABOVE&amp;gt;"&lt;/span&gt;
&lt;span class="py"&gt;migrations_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"drizzle/migrations"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that our database is online, we can apply our migrations and begin to interact with it! Drizzle takes care of this for us with the &lt;code&gt;drizzle-kit migrate&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run &lt;span class="nv"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production drizzle-kit migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By setting the &lt;code&gt;ENVIRONMENT&lt;/code&gt; variable to &lt;code&gt;production&lt;/code&gt;, we ensure that &lt;code&gt;drizzle.config&lt;/code&gt; uses our production configuration (and credentials) for the migration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’re following along, take a moment to inspect the database in &lt;a href="https://dash.cloudflare.com/" rel="noopener noreferrer"&gt;your Cloudflare dashboard&lt;/a&gt;. You should see all the tables defined in your schema, along with a Drizzle-generated migrations table.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We could jump straight to deploying our app now, but it wouldn’t have any data to show us, so first we’ll seed it with the data we’ve already generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database seeding
&lt;/h2&gt;

&lt;p&gt;Seeding databases is a key component of application development, but it can often seem like a cumbersome afterthought. Especially in a project’s early days, when speed is of the essence, structuring and re-structuring seed data as your database evolves feels thrash-y. Mocking database calls feels much cheaper in comparison, at least when it comes to testing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Seeding a database is the process of populating it with mock data that conforms to your spec. It’s useful throughout the product lifecycle: during development, testing, and demos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve worked on projects that mocked database calls, and “seeded” data for demos in the course of UI/UX testing. Setting aside the issues with using test data for demos, this approach quickly ran into scaling problems. In essence, we had traded managing database seeding for managing database mocks, which was no less cumbersome but much more fragile.&lt;/p&gt;

&lt;p&gt;Even if it feels like a lot at first, using seed data for development—and especially testing, is a game-changer. Notably, it makes integration tests a lot easier, and in my experience it can be easier to keep in sync with your database schema. In fact, tools like &lt;a href="https://orm.drizzle.team/docs/seed-overview" rel="noopener noreferrer"&gt;&lt;code&gt;drizzle-seed&lt;/code&gt;&lt;/a&gt; make this almost an afterthought by automatically generating seed data based on your latest schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeding Placegoose
&lt;/h2&gt;

&lt;p&gt;When I got started on Placegoose, &lt;code&gt;drizzle-seed&lt;/code&gt; had just been released. While intriguing, introducing it felt a bit like scope creep. If nothing else, it didn’t seem like it would support the goose theme I was going for, and solving &lt;em&gt;that&lt;/em&gt; problem was definitely out of scope.&lt;/p&gt;

&lt;p&gt;To create the seed data, I used a generative AI tool to create arrays of entries, which I saved in local project files. This worked well for Placegoose because the schema was essentially fixed, and there was no real need (or plans) to modify it. For most projects though, re-generating entries each time the schema changes would be cost-prohibitive, especially considering the amount of scrutiny AI-generated content requires.&lt;/p&gt;

&lt;p&gt;Determining the right path to insert the data was equally a function of the project’s requirements and constraints. Unlike many projects, which grow and evolve with time, Placegoose is meant to serve as an example, and as a tool that can easily be run (and modified) locally. Seeding the remote database with a local script was the clear answer: it’s a little more transparent, and it can be integrated into an automated build process if needed. &lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure constraints
&lt;/h3&gt;

&lt;p&gt;This is where things get a little more complicated. While Workers run in the &lt;code&gt;workerd&lt;/code&gt; runtime—and have access to bindings—any standalone scripts executed locally (or during the remote build ) run in a standard Node process. This means that there’s no way to directly call a remote D1 database via script.&lt;/p&gt;

&lt;p&gt;Executing the script in the worker global scope or creating a standalone worker to handle seeding are both options, but they introduce problems of their own. Notably, they would require gating access to the seeding logic, and creating an additional worker would add extra complexity with minimal return.&lt;/p&gt;

&lt;p&gt;Fortunately, we can also connect to D1 databases over HTTP. This allows us to pass our D1 credentials in the request, rather than relying on the binding. We can even keep using Drizzle to generate our SQL!&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeding with Drizzle’s HTTP proxy
&lt;/h2&gt;

&lt;p&gt;Drizzle offers an &lt;a href="https://orm.drizzle.team/docs/connect-drizzle-proxy" rel="noopener noreferrer"&gt;HTTP proxy driver&lt;/a&gt; that allows us to define how database requests are made, without modifying our query implementation.&lt;/p&gt;

&lt;p&gt;First though, we need to adjust our &lt;code&gt;drizzle.config&lt;/code&gt; so that &lt;code&gt;drizzle-kit&lt;/code&gt; uses the HTTP driver when running in a &lt;code&gt;production&lt;/code&gt; environment. The config definition is essentially the same: we only need to update the &lt;code&gt;driver&lt;/code&gt; and &lt;code&gt;dbCredentials&lt;/code&gt; properties, so that &lt;code&gt;drizzle-kit&lt;/code&gt; can connect to our remote D1 when applying migrations (or introspecting).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// We use dotenv to grab local environment variables&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-kit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;drizzleConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./.prod.vars&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Don't forget to update your local .prod.vars file!&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDFLARE_ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;databaseId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDFLARE_DATABASE_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDFLARE_D1_TOKEN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Make sure we actually set our credentials&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;databaseId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;`Configuration Failed: Missing Credentials`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;databaseId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;drizzleConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/schema.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./migrations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;dialect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqlite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d1-http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Use the HTTP driver&lt;/span&gt;
      &lt;span class="na"&gt;dbCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;databaseId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="c1"&gt;// Local config&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;drizzleConfig&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we’ll need to update our seed script to use the HTTP driver. This is slightly more involved, but still pretty straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing the HTTP driver
&lt;/h3&gt;

&lt;p&gt;The driver takes a callback responsible for making a single query, and an optional callback for making batches of requests. In the simplest case, the batch callback is just an iterative implementation of the single-query function we provide.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;drizzle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-orm/sqlite-proxy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Drizzle driver instance that we can use&lt;/span&gt;
&lt;span class="c1"&gt;// interchangeably with the d1 or libsql drivers&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;drizzle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * AsyncBatchRemoteCallback
     */&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
        &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;run&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;values&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="c1"&gt;// Execute a single query over HTTP&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * AsyncBatchRemoteCallback
     */&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;run&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;values&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}[]&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Iteratively execute an array of queries over HTTP&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Standard Drizzle config, instructs driver to
     * translate between snake and camel case column names
     */&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;casing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;snake_case&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we’re seeding relational data, we’ll want to take advantage of batches (which are executed as transactions), so we’ll need to implement both callbacks. Let’s start with the single-query case though, so we can get to know &lt;a href="https://developers.cloudflare.com/api/resources/d1/subresources/database/methods/query/" rel="noopener noreferrer"&gt;Cloudflare’s D1 HTTP query resource&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the most part, Cloudflare’s query API matches the &lt;code&gt;AsyncRemoteCallback&lt;/code&gt; function signature. Both accept a &lt;code&gt;sql&lt;/code&gt; string, an array of &lt;code&gt;params&lt;/code&gt;, and the &lt;code&gt;method&lt;/code&gt;, so we can just pass those values through directly. &lt;/p&gt;

&lt;p&gt;We’ll also need to specify the &lt;code&gt;accountId&lt;/code&gt;, &lt;code&gt;databaseId&lt;/code&gt;, and &lt;code&gt;apiToken&lt;/code&gt; for our remote D1, as the credentials we set in &lt;code&gt;drizzle.config&lt;/code&gt; are only used by &lt;code&gt;drizzle-kit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Below is a minimal implementation of the single-query callback. The &lt;code&gt;fetch&lt;/code&gt; request itself is extremely simple, but it takes a few steps to unpack the response and handle any errors. For more details on response typing, refer to &lt;a href="https://developers.cloudflare.com/api/resources/d1/subresources/database/methods/query/" rel="noopener noreferrer"&gt;the Cloudflare API docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AsyncRemoteCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drizzle-orm/sqlite-proxy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;D1HttpQueryResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}[];&lt;/span&gt;
    &lt;span class="nl"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}[];&lt;/span&gt;
    &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}[];&lt;/span&gt;
    &lt;span class="nl"&gt;success&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;httpQueryD1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AsyncRemoteCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.cloudflare.com/client/v4/accounts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/d1/database/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;databaseId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/query`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="cm"&gt;/** HTTP request failed */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Based on the Cloudflare docs&lt;/span&gt;
    &lt;span class="c1"&gt;// In practice, the type should be validated at runtime&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;dbResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;D1HttpQueryResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dbResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="cm"&gt;/** Query failed */&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dbResponse&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;queryResult&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cm"&gt;/** Query failed */&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Format row data&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queryResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unexpected Response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dbResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that since Cloudflare’s D1 &lt;code&gt;query&lt;/code&gt; endpoint returns results as JSON, we’ll need to flatten the values from each row into an array. Since the returned results are inherently &lt;code&gt;unknown&lt;/code&gt;, we can use an &lt;code&gt;instanceof&lt;/code&gt; check to let TypeScript know that each array element can be unpacked with &lt;code&gt;Object.values&lt;/code&gt;. While it might be tempting to save a few lines by casting to &lt;code&gt;any&lt;/code&gt;, &lt;a href="https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#any" rel="noopener noreferrer"&gt;doing so should be strictly avoided&lt;/a&gt;. The cost to your type safety is almost never justified.&lt;/p&gt;

&lt;p&gt;With our single-query callback implemented, creating a function to handle batch queries is fairly trivial. We just need to loop over the array of queries, pass the arguments into our single-query function, and push the returned rows into our &lt;code&gt;results&lt;/code&gt; array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;httpBatchQueryD1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AsyncBatchRemoteCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
      &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}[]&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;httpQueryD1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we try to run the script though, we run into an error! &lt;a href="https://developers.cloudflare.com/d1/platform/limits/" rel="noopener noreferrer"&gt;Cloudflare limits us to 100 bound variables&lt;/a&gt; for D1 queries. This means that we can insert no more than 20 rows of 5 columns each at a time (for example), assuming we’re only using variables to insert values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;400 Bad Request
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"errors"&lt;/span&gt;:[&lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"code"&lt;/span&gt;:7500,
        &lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"too many SQL variables at offset 420: SQLITE_ERROR"&lt;/span&gt;
    &lt;span class="o"&gt;}]&lt;/span&gt;,
    &lt;span class="s2"&gt;"success"&lt;/span&gt;:false
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chunking writes
&lt;/h3&gt;

&lt;p&gt;To work around this limit, we can seed our data in chunks. This is a common pattern for handling large inserts, and shouldn’t pose any problems for an app of this scale. To accomplish this, we can use a simple utility to break our data into appropriately-sized arrays. Remember, the formula for determining maximum chunk size is &lt;code&gt;100 / column count&lt;/code&gt;, so you may need to adjust your chunk size if a table’s column count changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Break data into appropriately-sized chunks&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunkArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(array: T[], size: number) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Writing relational data with transactions
&lt;/h3&gt;

&lt;p&gt;Ideally we would wrap our writes in a &lt;code&gt;transaction&lt;/code&gt;, ensuring they’d succeed or fail together. This is especially important when inserting relational data with inherent interdependencies between tables (and ID values). Unfortunately, there is an &lt;a href="//github.com/drizzle-team/drizzle-orm/issues/4212"&gt;open issue with the way Drizzle handles D1 transactions&lt;/a&gt;. While they work locally, they fail when writing to a remote D1 over HTTP.&lt;/p&gt;

&lt;p&gt;We can achieve the same effect, though, by using Drizzle’s &lt;code&gt;batch&lt;/code&gt; method. According to &lt;a href="https://orm.drizzle.team/docs/batch-api" rel="noopener noreferrer"&gt;Drizzle’s docs on D1 batches&lt;/a&gt;, they “execute and commit sequentially and non-concurrently”, and are fully-fledged SQL transactions.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;batch&lt;/code&gt; method effectively accepts an array of operations, but it’s typed as a tuple, so we’ll need to get a little creative when constructing the array. We’ll use a second utility to break our data into chunks, and create insert statements for each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunkInserts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;SQLiteTable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(
    table: T, 
    data: T["$inferInsert"][],
    batchSize: number,
) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataChunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chunkArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Initialize the array with the first insert to&lt;/span&gt;
  &lt;span class="c1"&gt;// satisfy the tuple type requirement&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunkedInserts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;BatchItem&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;BatchItem&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sqlite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataChunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// Loop starts at 1 as we've already added 0&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;dataChunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;batchItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataChunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nx"&gt;chunkedInserts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;batchItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;chunkedInserts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Seeding with the CLI
&lt;/h2&gt;

&lt;p&gt;Adding batches to the local seed script was minimally invasive, and got the job done. It was good enough for Placegoose, but it left me wondering whether there was a lower-touch solution.&lt;/p&gt;

&lt;p&gt;After some digging, I found that &lt;a href="https://developers.cloudflare.com/d1/wrangler-commands" rel="noopener noreferrer"&gt;Cloudflare’s CLI&lt;/a&gt; allows you to push data directly to a remote D1 database, without worrying about batching (or even scripting)! &lt;/p&gt;

&lt;p&gt;The first step is to initialize and seed a database locally. Then, export the database to a local SQL file using the &lt;code&gt;wrangler d1 export&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler d1 &lt;span class="nb"&gt;export &lt;/span&gt;placegoose-d1 &lt;span class="nt"&gt;--local&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; ./seed.sql &lt;span class="nt"&gt;--no-schema&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--no-schema&lt;/code&gt; flag ensures that only seed insert statements will be included in the generated file, since we’ll still use &lt;code&gt;drizzle-kit&lt;/code&gt; to push our migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler d1 migrations apply placegoose-d1 &lt;span class="nt"&gt;--local&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to push the SQL file to our remote D1. To do this, we’ll use the &lt;code&gt;wrangler d1 execute&lt;/code&gt; command, pointing to our &lt;code&gt;--output&lt;/code&gt; file from the export script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler d1 execute placegoose-d1 &lt;span class="nt"&gt;--remote&lt;/span&gt; &lt;span class="nt"&gt;--file&lt;/span&gt; ./seed.sql &lt;span class="nt"&gt;--yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don’t need to include the &lt;code&gt;--yes&lt;/code&gt; flag. This will automatically approve prompts that come up while the script runs. It may be useful if you choose to incorporate this approach into your build process, but it can have unintended consequences if you’re not sure what prompts will appear.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging schema issues
&lt;/h3&gt;

&lt;p&gt;When I first ran this script, the SQL file was uploaded successfully, but the remote execution failed. I got the following error message referring to a &lt;code&gt;sqlite_sequence&lt;/code&gt; table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;🌀 File already uploaded. Processing.

✘ &lt;span class="o"&gt;[&lt;/span&gt;ERROR] no such table: sqlite_sequence: SQLITE_ERROR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally this table is generated for us when we create a table with auto-incrementing columns. I forgot to make the ID columns in the Placegoose schema auto-incrementing though.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;autoIncrement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Simple fix&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a number of reasons, the right answer is to update the schema, but to be honest this didn’t occur to me until I had found a different solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding these lines to the top of the generated SQL file, I got D1 to create a &lt;code&gt;sqlite_sequence&lt;/code&gt; table without changing the actual schema. As a mock data API, Placegoose doesn’t actually support inserts, so I could have gotten away with this approach, but as a general rule, ignoring such fundamental problems with your database schema is a recipe for disaster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mapping migration tables
&lt;/h3&gt;

&lt;p&gt;A different change to the generated SQL file &lt;em&gt;was&lt;/em&gt; necessary though. Drizzle keeps track of migrations in a &lt;code&gt;__drizzle_migrations&lt;/code&gt; table, which was added to the remote D1 when we used &lt;code&gt;drizzle-kit&lt;/code&gt; to apply migrations. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.cloudflare.com/d1/reference/migrations/#wrangler-customizations" rel="noopener noreferrer"&gt;Wrangler &lt;em&gt;also&lt;/em&gt; keeps track of migrations in a &lt;code&gt;d1_migrations&lt;/code&gt; table&lt;/a&gt; though, and doesn’t have access to Drizzle’s migrations table. Consequently, the generated SQL file includes inserts to a &lt;code&gt;d1_migrations&lt;/code&gt;table that doesn’t exist on the remote. The schemas are the same though, so we just need to update the table name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;d1_migrations&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;__drizzle_migrations&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'0000_little_newton_destine.sql'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'2025-03-10 11:53:01'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’m sure there is a more elegant or hands-off way to approach this (let me know if you’ve found it), but my goal in researching this approach was to verify that it was possible, not to fine-tune it.&lt;/p&gt;

&lt;p&gt;After updating the database schema and updating the generated SQL file, we can push &lt;em&gt;all&lt;/em&gt; the seed data at once. We only have a few hundred records in total, so I’m not sure how this approach scales, but that volume of mock data is sufficient for many projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying a Worker
&lt;/h2&gt;

&lt;p&gt;Now that we have some data to display, let’s get our app deployed! As with creating our remote D1, it’s a really simple process. If you’ve been developing locally, you must already have &lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;installed Node&lt;/a&gt;, and have &lt;a href="https://developers.cloudflare.com/workers/wrangler/install-and-update/" rel="noopener noreferrer"&gt;Wrangler installed in your project&lt;/a&gt;, and it was necessary to create a Cloudflare account to create your remote D1.&lt;/p&gt;

&lt;p&gt;With the prerequisites out of the way, there’s just one more step: Run the &lt;a href="https://developers.cloudflare.com/workers/wrangler/install-and-update/" rel="noopener noreferrer"&gt;Wrangler deploy command&lt;/a&gt;! Note that using the &lt;code&gt;--minify&lt;/code&gt; flag can help reduce startup times, and keep the bundle within &lt;a href="https://developers.cloudflare.com/workers/platform/limits/#worker-size" rel="noopener noreferrer"&gt;Cloudflare’s Worker size limits&lt;/a&gt;. It can make debugging more difficult though, as minification typically obscures variable and function names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler deploy &lt;span class="nt"&gt;--minify&lt;/span&gt; src/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! Your project is live. It will be exposed at  &lt;code&gt;&amp;lt;project-name&amp;gt;.&amp;lt;cloudflare-user&amp;gt;.workers.dev&lt;/code&gt; by default, but you can &lt;a href="https://developers.cloudflare.com/workers/configuration/routing/custom-domains/" rel="noopener noreferrer"&gt;configure a custom domain&lt;/a&gt; in your &lt;code&gt;wrangler.toml&lt;/code&gt; or Cloudflare dashboard. &lt;/p&gt;

&lt;p&gt;If you end up using Placegoose as a starting point for your own mock data API, let me know in the comments! I’d also love to hear if there’s anything you wish I had covered in more detail, or if you’ve discovered a more streamlined approach!&lt;/p&gt;

&lt;h2&gt;
  
  
  What will you deploy next?
&lt;/h2&gt;

&lt;p&gt;As we’ve seen, deploying a simple backend to Cloudflare is pretty straightforward. We had to work around some infrastructure limits to seed our remote database, but the HONC stack offers a few solutions that meet our needs.&lt;/p&gt;

&lt;p&gt;Using Cloudflare’s CLI is a more direct approach and requires less configuration, but you may run into issues seeding larger datasets. Batching chunked writes is a more flexible solution, even though it requires a bit more code up-front.&lt;/p&gt;

&lt;p&gt;The optimal solution will depend on your use-case though. No matter which approach (or stack) you choose, you’ll run into some kind of limitations or constraints. To develop effective solutions, you’ll need to experiment with the tools your stack provides, and a clear understanding of your project’s requirements.&lt;/p&gt;

&lt;p&gt;The next steps are up to you! You may want to move on to deploying more complex backends, or make use of your mock data API to enhance your development experience. As with every software problem, success ultimately boils down to reading documentation and persistently experimenting with different approaches.&lt;/p&gt;

</description>
      <category>hono</category>
      <category>d1</category>
      <category>drizzle</category>
      <category>honc</category>
    </item>
    <item>
      <title>Hacking Hono: The Ins and Outs of Validation Middleware</title>
      <dc:creator>ambergristle</dc:creator>
      <pubDate>Mon, 03 Feb 2025 19:10:30 +0000</pubDate>
      <link>https://forem.com/fiberplane/hacking-hono-the-ins-and-outs-of-validation-middleware-2jea</link>
      <guid>https://forem.com/fiberplane/hacking-hono-the-ins-and-outs-of-validation-middleware-2jea</guid>
      <description>&lt;p&gt;&lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;Hono’s&lt;/a&gt; type system is one of its greatest strengths. If you don’t take some time to understand the basics though, it can prove to be a frustrating barrier to entry. As a newer framework, and one dedicated to flexibility, Hono’s official documentation mainly covers core concepts and base-cases. Dozens of official and community middleware and templates will steer you towards scalable and maintainable solutions, but the patterns and details are largely left to you.&lt;/p&gt;

&lt;p&gt;After working for years with meta-frameworks that seem to have an opinion about everything, this feels like a breath of fresh air. Hono’s middleware and helpers can be used out-of-box to meet many projects’ basic requirements, but it’s also remarkably simple to extend or replicate them to meet your project’s specific needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning Hono hands-on
&lt;/h3&gt;

&lt;p&gt;Hono’s &lt;a href="https://hono.dev/docs/guides/validation" rel="noopener noreferrer"&gt;approach to request validation&lt;/a&gt; is an ideal case study. Its core &lt;a href="https://github.com/honojs/hono/blob/d72aa4b6d77c7b3150bf2b7bae001e6635fe98ae/src/validator/validator.ts" rel="noopener noreferrer"&gt;&lt;code&gt;hono/validator&lt;/code&gt; implementation&lt;/a&gt; is only ~150 lines, half of which are imports and types. The logic itself is really straightforward, and can be trivially extended or modified locally. In fact, &lt;a href="https://hono.dev/examples/validator-error-handling#see-also" rel="noopener noreferrer"&gt;Hono’s validator-specific middleware&lt;/a&gt; are all built on &lt;code&gt;validator&lt;/code&gt;, and its types.&lt;/p&gt;

&lt;p&gt;If you want to get the most out of this article—and Hono—you’ll need to be comfortable with TypeScript and generics. You should be familiar with web API and TypeScript basics, but it’s ok if you’re a beginner, or prefer to use TypeScript sparingly: Hono’s types and utilities will do most of the heavy lifting for us.&lt;/p&gt;

&lt;p&gt;To get a clear picture of how Hono middleware works, we’ll implement the same validation middleware using three approaches, each peeling back a layer of abstraction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First with &lt;code&gt;@hono/zod-validator&lt;/code&gt;—the out-of-box solution,&lt;/li&gt;
&lt;li&gt;Then with &lt;code&gt;hono/validator&lt;/code&gt;—if you want to use your own validator, or bake in error processing,&lt;/li&gt;
&lt;li&gt;And finally with Hono’s &lt;code&gt;createMiddleware&lt;/code&gt;—not recommended for production, but a great way to take a closer look at how Hono works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hono’s &lt;code&gt;validator&lt;/code&gt; is especially powerful in combination with its RPC client, so we’ll also take a quick look at how route typing plugs into &lt;code&gt;hono/client&lt;/code&gt;. We won’t be covering OpenAPI integration—that deserves its own discussion—but most topics we address will be relevant in any middleware or handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Low-lift validation with &lt;code&gt;@hono/zod-validator&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If you’re already using a type-safe schema library, like Zod or TypeBox, and you just want to plug your schema in and go, Hono’s got you covered. You just install the relevant &lt;a href="https://hono.dev/examples/validator-error-handling#see-also" rel="noopener noreferrer"&gt;package-specific validation middleware&lt;/a&gt;, and it handles most of the boilerplate for you.&lt;/p&gt;

&lt;p&gt;I’m a long-time Zod fan, so we’ll start with Hono’s &lt;code&gt;zod-validator&lt;/code&gt;, but none of the examples or discussion will delve too deeply into Zod specifics. Instead, we’ll be focusing on what we can learn about Hono’s middleware typing from the package’s internals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sharing valid data with Context
&lt;/h3&gt;

&lt;p&gt;The first piece of Hono typing we really need to understand is the &lt;code&gt;Context&lt;/code&gt; object. Whether we’re using one of the dozens of official and community middleware—or creating our own—we’ll be working with &lt;code&gt;Context&lt;/code&gt;. It exposes the app environment—including any bindings for Cloudflare environments, the &lt;code&gt;Request&lt;/code&gt; and &lt;code&gt;Response&lt;/code&gt;, and a variety of helpers for reading and writing data. You can read more about those in &lt;a href="https://hono.dev/docs/api/context" rel="noopener noreferrer"&gt;the Hono &lt;code&gt;Context&lt;/code&gt; API docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="c1"&gt;// We'll get to these type parameters in a moment&lt;/span&gt;
    &lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Cloudflare bindings and env variables&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bindings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="c1"&gt;// Augmented Request with optionally-validated data&lt;/span&gt;
    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;req&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;HonoRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;res&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crucially, &lt;code&gt;Context&lt;/code&gt; allows us to &lt;a href="https://hono.dev/docs/guides/middleware" rel="noopener noreferrer"&gt;share data between middleware and handlers type-safely&lt;/a&gt;. This can be useful in a number of ways, but we’ll start with the &lt;code&gt;c.req.valid&lt;/code&gt; method, which allows us to access any request data validated by &lt;code&gt;validator&lt;/code&gt; (or middleware like &lt;code&gt;zod-validator&lt;/code&gt; that use it internally).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;zValidator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@hono/zod-validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// I like to centralize these in a directory like /dtos&lt;/span&gt;
&lt;span class="c1"&gt;// or /schemas, but it really depends on your use-case&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ZSearchQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// Must be handler-specific for type-safety&lt;/span&gt;
        &lt;span class="nf"&gt;zValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ZSearchQuery&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;// After going through middleware, Context&lt;/span&gt;
        &lt;span class="c1"&gt;// is passed into the handler&lt;/span&gt;
        &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// We know `search` is a string in this scope, so we&lt;/span&gt;
            &lt;span class="c1"&gt;// can handle the request type-safely from here on&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we pass &lt;code&gt;zValidator&lt;/code&gt; a target (&lt;code&gt;'query'&lt;/code&gt;) and a schema, the schema’s output becomes type-safely available in the handler (or subsequent middleware). Hono supports six validation targets, representing the most common formats for request data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;form&lt;/code&gt; (&lt;code&gt;multipart/form-data&lt;/code&gt; or &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;param&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;header&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cookie&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For simplicity, here we only validate the query string, but you can validate as many targets as you’d like by &lt;a href="https://hono.dev/docs/guides/validation#multiple-validators" rel="noopener noreferrer"&gt;chaining multiple validators&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For validated types to be inferred correctly, validation middleware &lt;em&gt;must&lt;/em&gt; be added in the handler, like in the example above. Chaining your validator with &lt;code&gt;app.use&lt;/code&gt; will result in the following TS error: &lt;br&gt;
&lt;em&gt;“Argument of type &lt;code&gt;string&lt;/code&gt; is not assignable to parameter of type &lt;code&gt;never&lt;/code&gt;.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Customizing the error hook
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;zod-validator&lt;/code&gt; package makes it really easy to enforce type-safety within handlers, but what happens when requests fail validation? By default, &lt;code&gt;zValidator&lt;/code&gt; immediately ends the request, returning a &lt;code&gt;400&lt;/code&gt; with a body containing the full &lt;code&gt;ZodError&lt;/code&gt; object. &lt;/p&gt;

&lt;p&gt;While convenient in development, it’s not great for production. This is especially true if you have internals that need obscuring (like auth flows), if you want to standardize error responses, or if your app has complex error-handling requirements (like logging or alerts).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Some OAuth flows use validation logic that takes the &lt;em&gt;unparsed&lt;/em&gt; body as input (typically in combination with a signature in the headers). In these cases, I’ve found its simplest to verify the request before moving on to validation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To override this default behavior, &lt;code&gt;zValidator&lt;/code&gt; accepts a third &lt;code&gt;hook&lt;/code&gt; argument: a callback that exposes the validation result and our good friend &lt;code&gt;Context&lt;/code&gt;. If validation fails, you can send a custom response, or throw to an error handler for additional processing.&lt;/p&gt;

&lt;p&gt;While it’s great to have this flexibility, responding to errors consistently makes APIs easier to build, troubleshoot, and work with. By abstracting the hook, we can reuse it whenever we validate, ensuring that invalid requests are handled the same way each time.&lt;/p&gt;

&lt;p&gt;This gets tedious quickly though, and can be difficult to maintain. To save ourselves the trouble of injecting our error hook each time we call &lt;code&gt;zValidator&lt;/code&gt;, we can instead bake it into a custom middleware.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ValidationTargets&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;zValidator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@hono/zod-validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Your custom error formatter&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatZodError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/zod-error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customZodValidator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="c1"&gt;// json, form, query, param, header, cookie&lt;/span&gt;
  &lt;span class="na"&gt;Target&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;keyof&lt;/span&gt; &lt;span class="na"&gt;ValidationTargets&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Schema&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;z&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ZodSchema&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(target: Target, schema: Schema) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;zValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Early-return (or throw) on error&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Error requirements will vary by use-case&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`invalid &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatZodError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Otherwise return the validated data&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the implementation is simple enough: &lt;code&gt;zValidator&lt;/code&gt; does the heavy lifting both at compile- and at run-time. We only need a few generics to make &lt;code&gt;zValidator&lt;/code&gt; aware of the argument types passed to our wrapper—essentially we’re prop-drilling the type—and it will take care of communicating with Hono’s type system.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’re not familiar with generics, I strongly recommend reading through the &lt;a href="https://www.typescriptlang.org/docs/handbook/2/generics.html" rel="noopener noreferrer"&gt;TypeScript Generics docs&lt;/a&gt;, or seeking out resources that better fit your learning style. &lt;strong&gt;TLDR?&lt;/strong&gt; They make types dynamic, helping us represent functions like &lt;code&gt;zValidator&lt;/code&gt; that return different results depending on argument subtypes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To allow for one-off routes with distinct error-handling requirements, we could also add an optional override hook. The typing for that is a little more complicated though, so we won’t get into that just yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  More flexibility with &lt;code&gt;hono/validator&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;First, let’s get a better understanding of how schema output types get from &lt;code&gt;zValidator&lt;/code&gt; to our handlers. Behind the curtain, it’s just a Zod-specific wrapper around &lt;code&gt;validator&lt;/code&gt;, and Hono offers &lt;a href="https://hono.dev/examples/validator-error-handling#error-handling-in-validator" rel="noopener noreferrer"&gt;equivalents for Typebox, Typia, and Valibot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you don’t see your favorite validator (or parser) on the list, or want to get creative with your error processing, fret not. It’s fairly trivial to reproduce the essentials yourself. Hono tools are built to be extended, and the source code is refreshingly accessible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// import { zValidator } from '@hono/zod-validator';&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;validator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono/validator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customZodValidator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="na"&gt;Target&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;keyof&lt;/span&gt; &lt;span class="na"&gt;ValidationTargets&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Schema&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;z&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ZodSchema&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(target: Target, schema: Schema) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// return zValidator(target, schema, (result, c) =&amp;gt; {&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// We have to run validation ourselves&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParseAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`invalid &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatZodError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing only three lines, we can update our example to remove the &lt;code&gt;@hono/zod-validator&lt;/code&gt; dependency, and decouple our logic from Zod. At this level of abstraction, you’re free to validate request data any way you’d like. You can then retrieve valid data in the handler, using &lt;code&gt;c.req.valid&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;How does this actually work though?&lt;/p&gt;

&lt;p&gt;When we use &lt;code&gt;validator&lt;/code&gt;, the callback’s (non-&lt;code&gt;Response&lt;/code&gt;) return type gets added to a type map, using the path, method, and target as keys. To achieve this, &lt;code&gt;validator&lt;/code&gt; leverages Hono’s &lt;code&gt;MiddlewareHandler&lt;/code&gt; type. Like &lt;code&gt;Context&lt;/code&gt;, &lt;code&gt;MiddlewareHandler&lt;/code&gt; takes three generic arguments, for &lt;code&gt;Env&lt;/code&gt;, path, and &lt;code&gt;Input&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MiddlewareHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// path&lt;/span&gt;
    &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/** */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Bindings&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Bindings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// object&lt;/span&gt;
    &lt;span class="nl"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// object&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="nl"&gt;out&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="nl"&gt;outputFormat&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;ResponseFormat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'json' | 'text' | 'redirect' | string&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Env&lt;/code&gt; and &lt;code&gt;Input&lt;/code&gt; are the type parameters you’ll manually work with the most. &lt;code&gt;Env&lt;/code&gt; exposes any environment variables (&lt;code&gt;Bindings&lt;/code&gt;), along with any values you’ve &lt;code&gt;set&lt;/code&gt; in &lt;code&gt;Context&lt;/code&gt; in your middleware (&lt;code&gt;Variables&lt;/code&gt;), while &lt;code&gt;Input&lt;/code&gt; represents any request data validated using &lt;code&gt;hono/validator&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This would be the &lt;code&gt;Input&lt;/code&gt; type for our simple search query, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Used by Hono internals, always same union&lt;/span&gt;
    &lt;span class="c1"&gt;// If you know what they do, let me know in the comments!&lt;/span&gt;
    &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ParsedFormValue&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ParsedFormValue&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// json: {};&lt;/span&gt;
        &lt;span class="c1"&gt;// form: {};&lt;/span&gt;
        &lt;span class="c1"&gt;// param: {};&lt;/span&gt;
        &lt;span class="c1"&gt;// header: {};&lt;/span&gt;
        &lt;span class="c1"&gt;// cookie: {};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;// Types you'll work with&lt;/span&gt;
    &lt;span class="nl"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Downstream &lt;em&gt;handlers&lt;/em&gt; use the &lt;code&gt;out&lt;/code&gt; type to determine which targets have been validated, and to appropriately type values returned from &lt;code&gt;c.req.valid&lt;/code&gt;. This is essentially Hono’s secret sauce: it uses generics to merge types into a format useable across the request lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a different validator
&lt;/h3&gt;

&lt;p&gt;All we really need then, is a target and an output type. We can easily update our custom Zod validator to accept a generic parse function (or one from a different library), as long as we make sure &lt;code&gt;validator&lt;/code&gt; generically knows the return type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Any validation function you provide must&lt;/span&gt;
&lt;span class="c1"&gt;// take unknown data and return data of a known type,&lt;/span&gt;
&lt;span class="c1"&gt;// or produce some kind of error&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ValidationFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;E&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customAgnosticValidator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="na"&gt;Target&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;keyof&lt;/span&gt; &lt;span class="na"&gt;ValidationTargets&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;T&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;Record&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;any&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&amp;gt;(target: Target, validate: ValidationFunction&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`invalid &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is ideal if you want to minimize your dependencies, or if you want to use an unsupported validator. Otherwise, the sturdiest (and most cost-effective) solution is to build on top of an existing package-specific Hono validator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting extra with &lt;code&gt;createMiddleware&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To get an even closer look, let’s take things a step too far, and implement our own version of &lt;code&gt;validator&lt;/code&gt;. While you wouldn’t want to do this for your validation layer, it will give us a chance to manually get and set values in &lt;code&gt;Context&lt;/code&gt;, which is really a game-changer for things like auth.&lt;/p&gt;

&lt;p&gt;To keep typing simple, we’ll take advantage of Hono’s &lt;code&gt;createMiddleware&lt;/code&gt; helper. This factory method ensures that your middleware typing can be read by subsequent handlers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Under the hood, createMiddleware is essentially a type utility. It doesn’t do any logic, but it does hook your middleware into Hono’s type system, which plays a key role in communicating types between middleware, handlers, and the Hono client. &lt;em&gt;It does not allow you to automatically &lt;a href="https://hono.dev/docs/guides/middleware#context-access-inside-middleware-arguments" rel="noopener noreferrer"&gt;share types between middleware&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of using the &lt;code&gt;Input&lt;/code&gt; type though, we’ll use the &lt;code&gt;Env&lt;/code&gt; type. There’s no way to set the data that’s available on &lt;code&gt;c.req.valid&lt;/code&gt; without using &lt;code&gt;validator&lt;/code&gt; (or forking Hono), but &lt;code&gt;Context&lt;/code&gt; comes with a getter and setter that we can use to type-safely share our own custom data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono/factory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;overEngineeredAgnosticValidator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="na"&gt;Target&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;keyof&lt;/span&gt; &lt;span class="na"&gt;ValidationTargets&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;T&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;Record&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&amp;gt;(target: Target, validate: ValidationFunction&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;) =&amp;gt; &lt;span class="si"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;createMiddleware&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Get and format target data from Request&lt;/span&gt;
      &lt;span class="c1"&gt;// https://github.com/honojs/hono/blob/b2affb84f18746b487a2e02f0b1cd18e2bd8e5f5/src/validator/validator.ts#L72&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getTargetData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid Payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;formatError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Get previously-validated data&lt;/span&gt;
        &lt;span class="c1"&gt;// `c.get('validated')` would also work&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// Update the validated data in context&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;validated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Don't forget to await. It's not necessary&lt;/span&gt;
        &lt;span class="c1"&gt;// until it is, and then it's a pain to retrofit&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we’re not using &lt;code&gt;validator&lt;/code&gt;, we won’t be able to access our data in the handler using &lt;code&gt;c.req.valid&lt;/code&gt;. Instead, we’ll use the &lt;code&gt;createMiddleware&lt;/code&gt; type generic to specify that we’ll be setting a &lt;code&gt;validated&lt;/code&gt; property in &lt;code&gt;Context&lt;/code&gt; variables, whose value is our parse result. We could then access our results in the handler like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;customValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ZSearchQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;// Context allows us to grab validated request data&lt;/span&gt;
        &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// `c.get('validated').query` would also work&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;// We know `search` is a string in this scope&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Querying validated endpoints with Hono RPC
&lt;/h2&gt;

&lt;p&gt;If your app has a front-end, Hono’s RPC client is a popular choice for keeping your types synced across your stack. It brings intellisense and type-safety to your request construction, representing resources as objects nested by path and method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hono/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// type AppType = typeof app;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Client uses type map to infer available endpoints&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BASE_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We can dot-index into the endpoint and method we want&lt;/span&gt;
    &lt;span class="c1"&gt;// and any `json` or `text` return types are inferred &lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="c1"&gt;// The client will let us know what data is required&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few gotchas to usage though, notably that the RPC client &lt;em&gt;only works with &lt;code&gt;json&lt;/code&gt; and &lt;code&gt;text&lt;/code&gt; responses.&lt;/em&gt; If your endpoint doesn’t return either, you can still use the client, but without the benefit of any additional type-safety. Moreover, if an endpoint returns &lt;em&gt;both&lt;/em&gt; &lt;code&gt;json&lt;/code&gt; and an incompatible method (e.g, &lt;code&gt;c.req.body&lt;/code&gt;), &lt;em&gt;none&lt;/em&gt; of the responses will be inferred.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that unlike a tool like &lt;code&gt;trpc&lt;/code&gt;, Hono’s RPC client isn’t linked to code instances, so shortcuts like &lt;code&gt;cmd+click&lt;/code&gt; in your code editor won’t take you to the handler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I haven’t worked with it extensively, so we’ll need to save a more in-depth discussion for another time, but it’s worth getting a sense of how the types inferred from our backend code get used by the client (and how they don’t). &lt;/p&gt;

&lt;h3&gt;
  
  
  Inferred request and response types
&lt;/h3&gt;

&lt;p&gt;As the client’s behavior suggests, all the (chained) middleware and handler types for an app or route are merged into a type map that’s keyed by endpoint and method, and includes the inferred input and union of output types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/posts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;$get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Success response&lt;/span&gt;
            &lt;span class="c1"&gt;// Request data validated with `hono/validator`&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="c1"&gt;// Data returned from handler&lt;/span&gt;
            &lt;span class="nl"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="nl"&gt;outputFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Error response&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="nl"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="nl"&gt;outputFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, we see that our posts endpoint requires a &lt;code&gt;search&lt;/code&gt; query value, and returns either a &lt;code&gt;200&lt;/code&gt; with some data, or a &lt;code&gt;400&lt;/code&gt; with an error message. Remember that only &lt;code&gt;text&lt;/code&gt; or &lt;code&gt;json&lt;/code&gt; responses returned from the handler will be included. Responses returned from middleware or helpers like &lt;code&gt;notFound&lt;/code&gt; or &lt;code&gt;onError&lt;/code&gt; are not included either. This behavior is not supported by Hono’s current type system, but it’s &lt;a href="https://github.com/honojs/hono/issues/2719" rel="noopener noreferrer"&gt;a known issue&lt;/a&gt; that may be addressed in the future.&lt;/p&gt;

&lt;p&gt;To get around this, you can explicitly set handler types yourself, though that’s not especially ergonomic, and is somewhat counterintuitive. The best solution will depend on your use-case, but using a standardized error response format combined with some custom type checking should easily bridge the gap for now.&lt;/p&gt;

&lt;p&gt;Regardless, it will often also be necessary to additionally parse data client-side: complex objects like &lt;code&gt;Date&lt;/code&gt; are serialized for HTTP requests, and clients generally don’t deserialize them for you. While this might seem cumbersome, it’s fairly trivial to add a validation layer around Hono’s client (or any TypeScript HTTP client) that transforms values as-needed. That, though, is a tomorrow problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s middleware all the way down
&lt;/h2&gt;

&lt;p&gt;For now, I hope that I’ve left you feeling excited to take your middleware to the next level, and confident to start exploring the &lt;a href="https://github.com/honojs" rel="noopener noreferrer"&gt;Hono source code&lt;/a&gt; if you haven’t already! It’s an amazing feat of engineering, and a great resource throughout the development process.&lt;/p&gt;

&lt;p&gt;Hono’s flexibility—which extends from its cross-runtime compatibility to its helper methods—opens the door to a highly composable and type-safe architecture. It’s simple to build on, introducing additional complexity and abstraction only as needed.&lt;/p&gt;

&lt;p&gt;This approach radically simplifies workflows—like auth and rate limiting—that often require data to be shared between multiple middleware layers before the request even hits the handler.&lt;/p&gt;

&lt;p&gt;I’m currently having a lot of fun building auth into a Hono app using the &lt;a href="https://lucia-auth.com/" rel="noopener noreferrer"&gt;new Lucia Auth guide&lt;/a&gt;, which is an awesome resource if you want to roll your own auth, or just learn more. I’m still ironing out some kinks, but I look forward to publishing an article on Hono auth in the coming months!&lt;/p&gt;

&lt;p&gt;Until then, if you need help with Hono or are seeking inspiration for your next project, check out the &lt;a href="https://discord.com/invite/KMh2eNSdxV" rel="noopener noreferrer"&gt;Hono Discord&lt;/a&gt;. I’ve found it to be an incredibly welcoming and supportive community, following the example set by the project authors, maintainers, and contributors.&lt;/p&gt;

</description>
      <category>hono</category>
      <category>middleware</category>
      <category>validation</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building data APIs with HONC</title>
      <dc:creator>ambergristle</dc:creator>
      <pubDate>Mon, 09 Dec 2024 17:03:59 +0000</pubDate>
      <link>https://forem.com/fiberplane/placegoose-building-data-apis-with-honc-id8</link>
      <guid>https://forem.com/fiberplane/placegoose-building-data-apis-with-honc-id8</guid>
      <description>&lt;p&gt;I was really excited when I came across Hono. I think the API is elegant in its simplicity, and I’ve found it—in my admittedly limited experience—to be a sturdy foundation for moderately complicated backends.&lt;/p&gt;

&lt;p&gt;In short, Hono is fast, flexible, and honestly fun to work with. Templates will get you started in a dozen different runtimes and frameworks, and there are a multitude of plugins and middleware to facilitate integration with third-party tools.&lt;/p&gt;

&lt;p&gt;How do all of these pieces fit together though? While project constraints and implementation details will vary, most data APIs need to satisfy three key requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A way to persist queryable data, typically a database,&lt;/li&gt;
&lt;li&gt;To define and regulate how data moves between application layers,&lt;/li&gt;
&lt;li&gt;And to safeguard against malicious activity and user error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you’re just getting started with a framework, ostensibly simple steps like configuring the database or spinning up a validation layer can become grueling hurdles to adoption. Enter &lt;a href="https://honc.dev/" rel="noopener noreferrer"&gt;HONC&lt;/a&gt;, a stack made for lightweight data APIs on the edge.&lt;/p&gt;

&lt;p&gt;HONC stands for Hono, Drizzle ORM, Neon DB, and Cloud(flare). It’s a collection of technologies dedicated to performance and type-safety, developed by &lt;a href="https://fiberplane.com/" rel="noopener noreferrer"&gt;Fiberplane&lt;/a&gt; as a template for non-trivial Hono APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  0 to 60 with the HONC app
&lt;/h3&gt;

&lt;p&gt;HONC is more of a design philosophy than a rigid doctrine. You can use the &lt;code&gt;create-honc-app&lt;/code&gt; CLI to download a project with either a Neon, D1, or Supabase DB. Drizzle plays a pivotal role by managing seeding and migrations, and decoupling the stack from the database. As the source of truth for (DB) type definitions, it’s can also be the foundation of your type system and runtime validation.&lt;/p&gt;

&lt;p&gt;This gets us from 0 to 60, but what about the 80/20, or at least 70/30? Implementation details like validation layers and rate limiting are too contingent on business requirements to usefully include in a template, but when you pick up a library for the first time, having a robust examples is a game-changer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mocking a (moderately) advanced data API
&lt;/h3&gt;

&lt;p&gt;To simulate what happens when a design philosophy collides with project constraints, Fiberplane asked me to build a simple mock-data API called &lt;a href="https://placegoose.fp.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Placegoose&lt;/strong&gt;&lt;/a&gt; (think &lt;a href="https://jsonplaceholder.typicode.com/" rel="noopener noreferrer"&gt;JSONPlaceholder&lt;/a&gt;) using the &lt;a href="https://honc.dev/" rel="noopener noreferrer"&gt;HONC&lt;/a&gt; stack.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://fiberplane.com/" rel="noopener noreferrer"&gt;Fiberplane&lt;/a&gt; is an API testing and debugging tool—like the Inspector panel in your browser—that we’ll be using to inspect requests, logs, and database calls.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A mock-data API’s functional requirements are robust enough to involve all key aspects of API development, but not so complicated as to be distracting. At a bare minimum, they serve relational data via multiple application layers, but they can be usefully enhanced with features like validation and rate limiting.&lt;/p&gt;

&lt;p&gt;To give ourselves some concrete parameters, we settled on a handful of features that most production-ready data APIs must implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A database with an ORM or custom adapter layer&lt;/li&gt;
&lt;li&gt;Validation and business logic&lt;/li&gt;
&lt;li&gt;Error handling and rate limiting&lt;/li&gt;
&lt;li&gt;&lt;em&gt;and a lightweight markdown-based frontend for docs&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the first article in a series that will cover 1) building the app, 2) deploying to production, and 3) rendering a front end. We hope the series has something to offer more- and less-experienced devs alike, but we’ll be focused on patterns, helpers, and gotchas, so we won’t be explaining basic data API or TypeScript patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting up and running with HONC
&lt;/h2&gt;

&lt;p&gt;To get started, we’ll download the &lt;a href="https://github.com/fiberplane/create-honc-app/tree/main/templates/d1" rel="noopener noreferrer"&gt;HONC D1 template&lt;/a&gt; and install dependencies. The project doesn’t need any additional configuration to run locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create honc-app@latest
npm i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to &lt;a href="https://developers.cloudflare.com/d1/get-started/#2-create-a-database" rel="noopener noreferrer"&gt;connect to a remote DB&lt;/a&gt;, you’ll need to update the D1 section in your &lt;code&gt;wrangler.toml&lt;/code&gt; (the Cloudflare Worker config file). We’ll cover this in detail in the next article. For now, take a moment to get acquainted with the config, and update the database name and ID to match your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[d1_databases]]&lt;/span&gt;
&lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DB"&lt;/span&gt;
&lt;span class="py"&gt;database_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"placegoose-d1"&lt;/span&gt;
&lt;span class="c"&gt;# Can be anything for local development&lt;/span&gt;
&lt;span class="c"&gt;# Must be updated when connecting to a remote DB&lt;/span&gt;
&lt;span class="py"&gt;database_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"local-placegoose-d1"&lt;/span&gt;
&lt;span class="py"&gt;migrations_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"drizzle/migrations"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;binding&lt;/code&gt; value is the key used to access the database from within the app. If you choose to rename it, be sure to keep the &lt;code&gt;Bindings&lt;/code&gt; property of &lt;code&gt;AppType&lt;/code&gt; in sync for proper intellisense and type propagation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Bindings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Global type from @cloudflare/workers-types&lt;/span&gt;
        &lt;span class="na"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;D1Database&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Any instances connecting to the DB must be typed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’re new to (or ambivalent about) TypeScript, don’t worry: Despite this being a fully-integrated TypeScript project, &lt;code&gt;AppType&lt;/code&gt; is one of the only types you’ll need to define and manage yourself! In fact, this is it for project setup, so why don’t we take a look at the HONC stack’s lynchpin: Drizzle ORM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type-safe database management with Drizzle ORM
&lt;/h2&gt;

&lt;p&gt;As I alluded to earlier, Drizzle does a lot of heavy lifting for us. In any project with a database, we need to manage table schemas and migrations, bridge the gap between JavaScript and SQL syntax, and validate data going into the DB.&lt;/p&gt;

&lt;p&gt;That’s a non-trivial task, especially for a small team or solo dev, and demands a lot of discipline to build and maintain. Drizzle offers all of this in a type-safe package that lets us derive types and validation models directly from table definitions.&lt;/p&gt;

&lt;p&gt;This schema-first approach is meant to ensure that updates are reflected across the stack, meaning fewer files to update and fewer migration bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining a single source of truth
&lt;/h3&gt;

&lt;p&gt;The HONC template comes with a single table definition (&lt;code&gt;db/schema.ts&lt;/code&gt;) that demonstrates how to require a column, default a value, and run raw SQL.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By default, Drizzle names columns after the keys in your table definitions. For seamless translation between camel and snake case, take advantage of Drizzle’s &lt;a href="https://orm.drizzle.team/docs/sql-schema-declaration#camel-and-snake-casing" rel="noopener noreferrer"&gt;automatic case conversion&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’ll update this file to describe the tables and types we need, in this case Gaggles, Geese (possibly belonging to a gaggle), and Honks (definitely belonging to a goose). This gives us a chance to check out foreign keys (&lt;code&gt;references&lt;/code&gt;), and share common column definitions (like primary keys or metadata) between tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sqliteTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drizzle-orm/sqlite-core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gaggles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sqliteTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gaggles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geese&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sqliteTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geese&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// Creating a Foreign Key&lt;/span&gt;
    &lt;span class="na"&gt;gaggleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;gaggles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tables also directly expose Insert and Select types inferred from the rules you’ve set for columns and keys. &lt;em&gt;Note that the SQL methods and features available (like enums) are syntax-specific.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GooseSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;geese&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$inferSelect&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geese&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sqliteTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geese&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gaggleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;gaggles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;isMigratory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;mood&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hangry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;waddling&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stoic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;haughty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alarmed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// type GooseSelect = {&lt;/span&gt;
&lt;span class="c1"&gt;//     id: number;&lt;/span&gt;
&lt;span class="c1"&gt;//     gaggleId: number | null;&lt;/span&gt;
&lt;span class="c1"&gt;//     name: string;&lt;/span&gt;
&lt;span class="c1"&gt;//     isMigratory: boolean;&lt;/span&gt;
&lt;span class="c1"&gt;//     mood: "hangry" | "waddling" | "stoic" | "haughty" | "alarmed" | null;&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If deployed effectively, these types will safeguard our data layer from code that tries to insert a malformed row. This doesn’t protect our API from bad data though, and it relies on comprehensive and disciplined typing throughout the app.&lt;/p&gt;

&lt;p&gt;To keep compile- and run-time types in sync, we’ll create a validation layer using the &lt;code&gt;drizzle-zod&lt;/code&gt; plugin. We’ll explore this in greater detail in the validation section, but first we need to seed our database!&lt;/p&gt;

&lt;h3&gt;
  
  
  Seeding the database at scale
&lt;/h3&gt;

&lt;p&gt;After I started working on the app, the HONC template was updated to use Drizzle’s new pRNG &lt;code&gt;drizzle-seed&lt;/code&gt; library. &lt;a href="https://orm.drizzle.team/docs/seed-overview#drizzle-seed" rel="noopener noreferrer"&gt;Drizzle Seed&lt;/a&gt; uses your table models to programmatically generate seed data and populate the database.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To create the seed data, &lt;a href="https://github.com/fiberplane/create-honc-app/tree/main/examples/placegoose" rel="noopener noreferrer"&gt;available in the repo&lt;/a&gt;, I fed the table models to a generative AI tool, making sure to test output at a small scale before dumping results into a json file. This was reasonably effective given that I only needed 500 rows, but obviously fragile, and somewhat tedious.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Placegoose does not currently use &lt;code&gt;drizzle-seed&lt;/code&gt;, but this is how I would refine data generation for the Gaggles table. While not exhaustive, the helper functions that Drizzle provides are robust enough to support data like blog posts, contact information, or job listings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/src/db/schema.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;drizzle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="na"&gt;gaggles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gaggles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ... &lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;refine&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;gaggles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;territory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;weightedRandom&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;city&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With our table models and seeding refinements (or seed data) defined, we just need to 1) create a local database, 2) generate and apply the initial migration, and 3) run the seed script. The HONC template includes a package script that chains these operations together, making it as easy as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run db:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Managing request data
&lt;/h2&gt;

&lt;p&gt;Now that we have some data in the DB, we can start writing and testing endpoints! Hono’s approach to modular routes will be familiar to anyone that’s worked with Express: Just create a new Hono instance, chain whatever middleware and handling logic you need, and pass the instance as the second argument of &lt;code&gt;Hono.route&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cors&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hono/cors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;instrument&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@fiberplane/hono-otel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Ensure the API is publicly accessible.&lt;/span&gt;
&lt;span class="c1"&gt;// For more, see MDN's docs on CORS.&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gagglesRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// This handler will not be inferred in the gagglesRoute type&lt;/span&gt;
&lt;span class="nx"&gt;gagglesRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not yet implemented&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;418&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/gaggles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gaggles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Have Fiberplane client inspect traces&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hono recommends chaining methods directly to the constructor call for optimal type inference and RPC behavior. I chose to separate method calls because this project doesn’t benefit from the additional type safety, and I find them easier to read.&lt;/p&gt;

&lt;p&gt;Writing all our handlers in the index file would quickly get out of hand though, so we should add a &lt;code&gt;routes&lt;/code&gt; directory with a file for each resource, limiting the cognitive load in a given file.&lt;/p&gt;

&lt;p&gt;The next steps are to develop the handler logic and validation, filling in routes one endpoint at a time. This is where we’ll start to run into errors, so remember to add a simple catch-all error handler. We’ll cover error processing in more detail later, but we don’t want our app to crash while we work!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Visualizing the data pipeline with Fibeplane Studio
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’re following along locally, take a moment to launch your app and the Fiberplane Studio by running &lt;code&gt;npm run dev&lt;/code&gt; and &lt;code&gt;npm run fiberplane&lt;/code&gt; in separate terminals!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To debug requests that go wrong, and to optimize services that are working as expected, we can use the Fiberplane Studio. By wrapping our app with the &lt;code&gt;instrument&lt;/code&gt; method, we give the Fiberplane client access to request traces, which are then displayed in the Studio.&lt;/p&gt;

&lt;p&gt;Built specifically for Hono apps, Fiberplane automatically detects new routes and configures request templates with path parameters. As you test (and re-test) services, console logs of all levels will appear in the Logs panel (hotkey &lt;code&gt;G + L&lt;/code&gt;), and we can inspect the request Timeline (&lt;code&gt;G + T&lt;/code&gt;) to view all traces, including D1 database calls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwskgbe4wmmec18p46454.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwskgbe4wmmec18p46454.png" alt="Fiberplane Studio Screenshot" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like most mainstream HTTP clients you can “replay” requests, making it a piece of cake to rapidly test defined happy and sad paths after refactoring an endpoint. By integrating logs and more robust traces though, I found that Fiberplane cut down on some of the back-and-forth between my HTTP client and my terminal.&lt;/p&gt;

&lt;p&gt;Having this comprehensive insight into the request lifecycle built into my HTTP client was helpful throughout development, but especially when building in more complex features like validation, and when trying to optimize handler performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Querying the bound databases &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;With telemetry set up, we’re ready to start querying and serving data! First, we need to connect to the database by calling the &lt;code&gt;drizzle&lt;/code&gt; initializer, which expects a D1 client bound to the app. This is where the &lt;code&gt;Bindings&lt;/code&gt; property on &lt;code&gt;AppType&lt;/code&gt; comes in. Hono exposes bindings and other environment values through the &lt;code&gt;Context&lt;/code&gt; object, whose typing is inherited from its immediate parent.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the source code I abstract the call in order to reduce repetition, but the following examples will show it inline for clarity. Though tempting, I chose not to use a singleton because there didn’t seem to be much benefit for such a simple service and short-lived service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The initializer also accepts an optional &lt;code&gt;config&lt;/code&gt; argument. Since we’re making use of Drizzle’s auto-casing, we need to specify that the client should expect snake case from the DB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;drizzle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drizzle-orm/d1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AppType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Bindings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Global type we get from @cloudflare/workers-types&lt;/span&gt;
        &lt;span class="na"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;D1Database&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gagglesApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Hono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Get all Gaggles&lt;/span&gt;
&lt;span class="nx"&gt;gagglesApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We get our DB binding from Context&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;drizzle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// This must be set for Drizzle to automatically&lt;/span&gt;
        &lt;span class="c1"&gt;// translate between snake and camel case&lt;/span&gt;
        &lt;span class="na"&gt;casing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;snake_case&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Drizzle inference tells us is type Gaggle[]&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gaggles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gaggles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gaggles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;gagglesApp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drizzle ORM aims to be a lightweight abstraction over SQL, so query construction is fairly intuitive. Statements are represented as chains of keywords, and Drizzle exports operators as flavor-specific helper methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforcing Drizzle types at run-time
&lt;/h3&gt;

&lt;p&gt;To keep compile- and run-time types in sync, we’ll create a validation layer using the &lt;code&gt;drizzle-zod&lt;/code&gt; plugin. It gives us constructors that build Zod schemas from Drizzle table models. As with the types exposed on table models, there is an Insert and a Select option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createInsertSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drizzle-zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// src/db/validation.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ZGaggleInsert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createInsertSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gaggles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;territory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;territory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially I was worried this might be limiting, but Drizzle makes it easy to extend or override field definitions. Zod accepts empty string values by default, so I made use of this feature to require that name fields were at least populated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Zod is an awesome schema library you can use to keep your types and validation in sync. I won’t be discussing how to use the library here, but I encourage you to check out the &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; if you haven’t already!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I chose to export all the Zod schemas from a single file in the &lt;code&gt;db&lt;/code&gt; directory, mostly to keep them out of the schema file exports, but also because I’ve found that keeping validators centralized and close to where they’re used—in this case the data layer—helps maintain a clear distinction between different validation layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validating request data
&lt;/h3&gt;

&lt;p&gt;Since the app is meant to serve mock data, a more defined database validation layer wasn’t necessary, but we do want to validate incoming payloads.&lt;/p&gt;

&lt;p&gt;The Insert schemas we generated from our tables are fine for the DB, but we don’t want to allow users to specify ID values themselves, as this can quickly get messy. We also want to prevent users from updating which goose honked a honk, because what kind of world would that be! To deal with this, we can create an additional validation layer for request payloads in a new &lt;code&gt;dtos&lt;/code&gt; directory. DTOs (Data Transfer Objects) are just logic that regulates how data moves across layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ZGaggleInsertPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ZGaggleInsert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ZHonkInsertPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ZHonkInsert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ZHonkUpdatePayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ZHonkInsertPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;gooseId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hono’s &lt;code&gt;validator&lt;/code&gt; middleware makes it easy to use these schemas (or any logic) to validate request data. Targets include—but aren’t limited to—route parameters, query values, and json (body). The middleware takes the target (e.g., “param”) and a validation callback, and exposes valid results type-safely via the app Context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Update Gaggle specified by id&lt;/span&gt;
&lt;span class="nx"&gt;gagglesApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;param&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;1-9&lt;/span&gt;&lt;span class="se"&gt;]\\&lt;/span&gt;&lt;span class="sr"&gt;d*$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ID values must be positive integers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nf"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 'id' is known to be type "number"&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;param&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedGaggle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hono provides a Zod-specific validator helper (&lt;code&gt;@hono/zod-validator&lt;/code&gt;), which takes care of the validation and error handling boilerplate. I found the library to be a useful reference, but by default it early-returns the response on error—including full Zod error details in the body.&lt;/p&gt;

&lt;p&gt;You can override this behavior with a callback, but I opted to build my own solution in order to directly incorporate standardized error processing, and maintain a centralized error handling flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * @returns Validation fn for Hono body validator, responsible
 * for processing payload errors
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;makeBodyValidator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Zod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AnyZodObject&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// _output is a utility key on Zod schema types&lt;/span&gt;
    &lt;span class="c1"&gt;// that gives us the type of valid output&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Return value must be consistent with shape of "body"&lt;/span&gt;
      &lt;span class="c1"&gt;// Available through Context.req.valid&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid Payload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also wrote a simple function to &lt;a href="https://zod.dev/?id=error-handling" rel="noopener noreferrer"&gt;format Zod error data&lt;/a&gt; so that it would be more useful for consumers. In retrospect, I should have called this in the body validator factory, and included the results in a &lt;a href="https://hono.dev/docs/api/exception#throw-httpexception" rel="noopener noreferrer"&gt;custom error response&lt;/a&gt;. I didn’t realize you could inject responses like this at the time though, and I chose not to extend Hono’s &lt;code&gt;HTTPException&lt;/code&gt; in the interest of simplicity. Instead, I threw to the global error handler we’ll set up next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling the sad path
&lt;/h2&gt;

&lt;p&gt;Securing vulnerabilities and handling errors are critical components of API development, and we’ve already taken a few important steps: Drizzle lets us keep our type system slim, maintainable, and in sync with runtime validation, preventing typing bugs at compile-time. We then use Hono’s &lt;code&gt;validator&lt;/code&gt; middleware to enforce these data contracts at run-time, ensuring our handlers are working with valid data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Responding to errors gracefully &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;When something goes wrong though, we need to communicate that to users and system maintainers in a way that’s helpful to each. While a detailed error log is useful to devs, it can be overwhelming to consumers, and can leak sensitive information about users or the system.&lt;/p&gt;

&lt;p&gt;We can create a catch-all error handler using the &lt;code&gt;Hono.onError&lt;/code&gt; method after all our route definitions. It gives us access to the error data and request context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Handle formatted errors thrown by app or hono&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I typically prefer to centralize error processing because it makes it easy to create a consistent experience. You can create error boundaries wherever you’d like though, both with &lt;code&gt;onError&lt;/code&gt;, and the specialized &lt;code&gt;Hono.notFound&lt;/code&gt; handler. I’ve found this especially powerful when creating webhooks for multiple third-party services, which might have different error-handling requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using rate limiting to protect APIs from abuse
&lt;/h3&gt;

&lt;p&gt;Securing our app isn’t just about data integrity though, it’s also about access. Now that our app is ready for consumers, we need to make sure that they all have fair access to the service. Rate limiters track how often users make a request—typically with a low-latency database like Redis—and reject requests if they occur too frequently within a set period.&lt;/p&gt;

&lt;p&gt;Controlling how often services are accessed allows us to prevent users from hogging resources (maliciously or not), possibly slowing down and even crashing systems. If your service will be paywalled, some form of rate limiting is also the only way to enforce tiered access.&lt;/p&gt;

&lt;p&gt;Since we’re using Cloudflare, we can take advantage of their new Rate Limiting bindings, now in open beta. This product handles both the rate limiting logic and data storage, based the configuration in your &lt;code&gt;wrangler.toml&lt;/code&gt;. The calls we anticipate in this app are pretty cheap on the whole, so we can afford to be liberal with the rate limit and period.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# The rate limiting API is in open beta.&lt;/span&gt;
&lt;span class="nn"&gt;[[unsafe.bindings]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MY_RATE_LIMITER"&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ratelimit"&lt;/span&gt;
&lt;span class="c"&gt;# An identifier you define, that is unique to your Cloudflare account.&lt;/span&gt;
&lt;span class="c"&gt;# Must be an integer.&lt;/span&gt;
&lt;span class="py"&gt;namespace_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1001"&lt;/span&gt;

&lt;span class="c"&gt;# Limit: the number of tokens allowed within a given period in a single&lt;/span&gt;
&lt;span class="c"&gt;# Cloudflare location&lt;/span&gt;
&lt;span class="c"&gt;# Period: the duration of the period, in seconds. Must be either 10 or 60&lt;/span&gt;
&lt;span class="py"&gt;simple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;period&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After configuring it, we would call the binding’s &lt;code&gt;limit&lt;/code&gt; method from a custom middleware to determine whether to proceed with a request. Under the hood, the rate limiter will query an in-memory DB with the provided &lt;code&gt;key&lt;/code&gt;, and use the results to determine whether the client (represented by the &lt;code&gt;key&lt;/code&gt;) is eligible to make additional requests in the current period.&lt;/p&gt;

&lt;p&gt;It is recommended that you use a unique key for each user, like an ID, in order to ensure that each user gets the expected number of requests per period.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MY_RATE_LIMITER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USER_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to conform to the HTTP spec, rejected responses must include data (like the &lt;code&gt;retry-after&lt;/code&gt;) header. Rather than implement the spec ourselves, we can lean on the rich ecosystem of third-party solutions that is beginning to emerge around Hono.&lt;/p&gt;

&lt;p&gt;We’ll be using &lt;code&gt;hono-rate-limiter&lt;/code&gt;, which is also compatible with other storage solutions (like Cloudflare KV and Redis). It manages all the rate limiting logic and storage for us, and formats responses to rejected requests appropriately. After installing, we only need to configure access to the binding and a key generation method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The Cloudflare rate limiter is distributed as a separate package&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cloudflareRateLimiter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hono-rate-limiter/cloudflare&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;cloudflareRateLimiter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;rateLimitBinding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RATE_LIMITER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyGenerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// IPv4 or IPv6&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getConnInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given the project’s limited scope—and the absence of defined users—we can use the request IP as the store key. Hono’s &lt;code&gt;getConnInfo&lt;/code&gt; helper provides easy access to protocol and address info. Since users could legitimately share an IP though, having a unique token for each user (like a user ID) would be critical for a paywalled or heavily-trafficked API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building on the HONC stack
&lt;/h2&gt;

&lt;p&gt;As a mock data API, Placegoose didn’t have a clear need for auth, or any kind of user or token management. Nonetheless, these are indispensable components of a secure data API. Hono offers some simple auth middleware, but if you need a more robust solution, or if you’re interested in rolling your own, I highly recommend &lt;a href="https://lucia-auth.com/" rel="noopener noreferrer"&gt;Lucia Auth&lt;/a&gt;, an open source learning resource for session-based auth. They provide great guidelines, and examples for most common frameworks.&lt;/p&gt;

&lt;p&gt;There was a lot I couldn’t cover in this article, but I hope that I’ve highlighted how the HONC stack can be used to address key requirements for lightweight data APIs, namely persistence, data integrity, and system security. Its minimal footprint helps it leverage performance on the edge, while its schema-first approach to typing streamlines system stability and maintainability.&lt;/p&gt;

&lt;p&gt;Above all, the HONC stack is a strong but flexible framework, into which we can easily integrate important features like validation and rate limiting without losing type safety.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/fiberplane/placegoose-seeding-and-deployment-with-honc-5f88"&gt;the next article&lt;/a&gt;, I cover deploying Placegoose to production, including how to seed a remote D1. To conclude the series, we’ll discuss using markdown to render API docs with a custom layout.&lt;/p&gt;

</description>
      <category>hono</category>
      <category>drizzle</category>
      <category>api</category>
      <category>honc</category>
    </item>
  </channel>
</rss>
