<?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: Iain McHugh</title>
    <description>The latest articles on Forem by Iain McHugh (@iain_mchugh_9e9eb7b4778f0).</description>
    <link>https://forem.com/iain_mchugh_9e9eb7b4778f0</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%2F3594084%2Fed0d46f3-aa11-4883-8ba4-6e980e21daa5.jpg</url>
      <title>Forem: Iain McHugh</title>
      <link>https://forem.com/iain_mchugh_9e9eb7b4778f0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/iain_mchugh_9e9eb7b4778f0"/>
    <language>en</language>
    <item>
      <title>File based queries with tanstack start</title>
      <dc:creator>Iain McHugh</dc:creator>
      <pubDate>Mon, 17 Nov 2025 13:09:40 +0000</pubDate>
      <link>https://forem.com/iain_mchugh_9e9eb7b4778f0/file-based-queries-with-tanstack-start-27d1</link>
      <guid>https://forem.com/iain_mchugh_9e9eb7b4778f0/file-based-queries-with-tanstack-start-27d1</guid>
      <description>&lt;p&gt;Just want to see the code?&lt;br&gt;
&lt;a href="https://github.com/IainMcHugh/start-r-query" rel="noopener noreferrer"&gt;https://github.com/IainMcHugh/start-r-query&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having used tanstack start for some months now, it has definitely become my preferred application starter in terms of developer experience. There is something about the end-to-end router type safety that scratches an itch my brain never knew it had. When you extract type safe query parameters from &lt;code&gt;Route.useParams()&lt;/code&gt; that you validated as part of the &lt;code&gt;createFileRoute()&lt;/code&gt;, something clicks that simply cannot be un-clicked.&lt;/p&gt;

&lt;p&gt;What's more, it has provided me with a great ingress into the world of vite as a build system solution. The automatic boilerplate code that gets created when adding a new route file seems like such a quirky feature in some ways but I absolutely love it. How the &lt;code&gt;routerTree.gen.ts&lt;/code&gt; is exposed for all those curious enough to dare open it - is a refreshing touch in the modern world of magical meta frameworks that are getting more and more complex.&lt;/p&gt;

&lt;p&gt;Also as a massive fan of tanstack query, I am super impressed by the developer experience provided via tanstack start that allows for server side fetching while keeping the familiar &lt;code&gt;useQuery&lt;/code&gt; API when in client side react space. Take a look at this boilerplate code provided when creating a new tanstack start app with tanstack query:&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;// src/routes/posts.$postId.tsx&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;Route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createFileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/posts/$postId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;loader&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="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;postId&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;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureQueryData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;postQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&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;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PostComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; 

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PostComponent&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;postId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useParams&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSuspenseQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;postQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What stuck out to me from this snippet when I first came across it is the slight departure from how I was used to using tanstack query. Specifically, I was used to defining a custom hook per query, so something 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;usePostById&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useQuery&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;But a problem arises here when you want to lean into the newer features/hooks provided by the library. For example, we can't easily swap in &lt;code&gt;useSuspenseQuery&lt;/code&gt; when the usage of &lt;code&gt;useQuery&lt;/code&gt; has been baked into our custom query hook. This is where another seemingly simple yet genius detail of tanstack query's design comes to light:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the query object is the same API across all query related functions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So what we changed here, is to simply define and expose just the query object, and let the relevant function/hook instantiation be determined at the instance of usage. There is even a helper function to ensure you get intelli-sense for the object parameters:&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;queryOptions&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;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postQueryOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&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="nf"&gt;queryOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&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;With this subtle change, we can do:&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="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;postQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nf"&gt;useSuspenseQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;postQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nf"&gt;ensureQueryData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;postQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// and simple overrides&lt;/span&gt;
&lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;postQueryOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;initialData&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;and this works perfectly because the &lt;code&gt;queryKey&lt;/code&gt; is shared 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Great, so where do I put this query object?
&lt;/h2&gt;

&lt;p&gt;Well, similar to custom query hooks, I often find myself asking this question - where should they live? I've seen two approaches here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Co-locate with usage. Put the query object near the code that uses it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the caveat that if it starts getting used in a second place within your application, lift up where you define it to a sensible place. Now most of the time I fall into the keep-it-simple/co-location camp but there are reasons here it just seems sub-optimal. Queries are used in features, and would therefor get colocated with features. To me, features are business logic code and should not be optimised for re-usability. I have seen much advice contrary to this so let me explain. &lt;/p&gt;

&lt;p&gt;Lets say we have a &lt;code&gt;BlogPostCard&lt;/code&gt;component feature, the part of it we should abstract for re-usability is the &lt;code&gt;Card&lt;/code&gt; part, and the &lt;code&gt;BlogPost&lt;/code&gt; part is the feature implementation. I think of it as the &lt;code&gt;Card&lt;/code&gt; being something developers/designers need to enforce as a shared UI/UX standard, and the &lt;code&gt;BlogPost&lt;/code&gt; part being the domain of product managers. Products and features constantly change their shape and where they live, and this is where our query logic should reside (NOT in the &lt;code&gt;Card&lt;/code&gt;), so it is inevitable to me we will have to constantly revisit moving query logic around as an application changes. And what happens inevitably is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers are averse to moving logic like this&lt;/li&gt;
&lt;li&gt;query logic has no clear definition point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to honestly, a bit of a mess in application structure. So back to the second approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Folder structure specific to queries/API stuff&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ultimately I think this is the way to go, but the problem now becomes &lt;em&gt;what folder structure?&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Within this I have seen two approaches, although I am sure there are more - one to match the path parameters of the API being called to your folder structure, eg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// for API calling /api/v2/member-data/posts/post/${postId}&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
  &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;
    &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt;
      &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
        &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;
          &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;
            &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;$postId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="c1"&gt;// contains fetcher fn, query obj etc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not the worst but can lead to some ugly folder structures that can be hard to traverse, and seems brittle (what happens when &lt;code&gt;v2&lt;/code&gt; becomes &lt;code&gt;v3&lt;/code&gt;?). I do like that it's easy to find where the relevant code lives provided you have the API path you're looking for, but there's no guarantee the structure will be descriptive of the content being fetched (more often that not it isn't) which is a big negative. This nicely leads to a second approach, define by domain relevant to the frontend - so something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data/Posts.ts
data/Users.ts
data/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and so on. At first glance, this can seem like a clean solution - but when I see the words &lt;code&gt;clean&lt;/code&gt; and &lt;code&gt;code&lt;/code&gt; in close proximity alarm bells go off in my head. Clean code stops being clean once real problems arise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some APIs refer to &lt;code&gt;posts&lt;/code&gt; as &lt;code&gt;blogs&lt;/code&gt;, what should our domain be called in this case?&lt;/li&gt;
&lt;li&gt;These 8 different APIs all return slightly different variants of the user, how do we handle this to have a singular clean user?&lt;/li&gt;
&lt;li&gt;When fetching posts by user, should this live with users or posts (and vice-versa)?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think both of these approaches have their pros and cons, what if there was some best-of-both-worlds solution here?&lt;/p&gt;

&lt;h2&gt;
  
  
  What about a folder structure based on query keys?
&lt;/h2&gt;

&lt;p&gt;This struck me as a really neat solution for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a solution that decouples where our query code lives from the UI code - a must have for scalable and evolving applications&lt;/li&gt;
&lt;li&gt;We define our own query keys, so we get the &lt;em&gt;cleanliness&lt;/em&gt; benefits of approach #2 outlined above (provided we use descriptive patterns for query keys)&lt;/li&gt;
&lt;li&gt;The query key is what we care about! What I mean by this is, after defining a &lt;code&gt;queryObject&lt;/code&gt;, our two uses for it practically end up being:

&lt;ol&gt;
&lt;li&gt;To query the relevant data&lt;/li&gt;
&lt;li&gt;To invalidate said query (which requires the query key!)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this begins to address what is really one of the only pain points I have come across with RQ: query key invalidation for a query that no longer exists. How does this happen? Let's say you have the following setup:&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;// .../queryKey.tsx&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;queryKeyFactory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;someQuery&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="s1"&gt;some&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="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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// .../query.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useSomeQuery&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;queryKeyFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;someQuery&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;// .../mutation.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useSomeMutation&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...,&lt;/span&gt;
    &lt;span class="na"&gt;onSuccess&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="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;queryKeyFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;someQuery&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have seen code where that &lt;code&gt;useSomeQuery&lt;/code&gt; is removed but the associated &lt;code&gt;someQuery&lt;/code&gt; key is left behind (it shouldn't be but it happens). There is no indication that the &lt;code&gt;onSuccess&lt;/code&gt; handler is effectively doing nothing. To me this problem screams out for some rescuing by typescript, and I think together with vite's plugin capabilities we can solve this. Let's go one step further and lean into the pattern used for routing but apply it to configuring queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  File based queries
&lt;/h2&gt;

&lt;p&gt;Okay, let's go step-by-step. We have an API that we want to make available via RQ, lets define our folder structure based on query keys:&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;// queries/posts/$postId.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point (assuming the vite server is up and running), and with a shiny new vite plugin defined here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nf"&gt;tanstackStart&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;viteReact&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="c1"&gt;// new!&lt;/span&gt;
  &lt;span class="nf"&gt;viteDataQueryScaffoldPlugin&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;That kicks in and automatically populates our new &lt;code&gt;ts&lt;/code&gt; file with boiler-plate code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// queries/posts/$postId.ts&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;createDataQuery&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;~/lib/createDataQuery&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;post$postIdQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createDataQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;posts/$postId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt; &lt;span class="p"&gt;}})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/v2/posts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postId&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens behind the scenes of &lt;code&gt;createDataQuery&lt;/code&gt; is that our queryKey is automatically defined based on the path provided, so &lt;code&gt;["posts", postId]&lt;/code&gt;. It then exposes the query objects as a parameter to the callback function but it attaches the &lt;code&gt;postId&lt;/code&gt; as options to the &lt;code&gt;queryFn&lt;/code&gt; (which has it's type inferred from the path!). This function exposes two things:&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 query object&lt;/span&gt;
&lt;span class="nx"&gt;posts$postIdQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;// the key, ["posts", postId]&lt;/span&gt;
&lt;span class="nx"&gt;posts$postIdQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are type-safe, and deleting the &lt;code&gt;createDataQuery&lt;/code&gt; instance will result in any uses of the &lt;code&gt;key&lt;/code&gt; or &lt;code&gt;options&lt;/code&gt; above erroring out! Going back to our original boilerplate code would look something 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="c1"&gt;// src/routes/posts.$postId.tsx&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;Route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createFileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/posts/$postId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;loader&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="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;postId&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;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ensureQueryData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;posts$postIdQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;postId&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;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PostComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; 

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PostComponent&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;postId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useParams&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSuspenseQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;posts$postIdQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;options&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I love this solution for the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File-based routing is always going to be more interpretable than programmatic architecture.&lt;/li&gt;
&lt;li&gt;It aligns nicely with the router paradigm&lt;/li&gt;
&lt;li&gt;It removes the question of where should this layer of code live&lt;/li&gt;
&lt;li&gt;File based solutions allow for easy tap in to vite's plugin system with &lt;code&gt;handleHotUpdate&lt;/code&gt;, allowing for next level type safety!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So what's next?
&lt;/h2&gt;

&lt;p&gt;I am sharing this approach to get feedback but if there is a world where it could be internalised within tanstack start, that'd be amazing. It doesn't involve any changes internal to tanstack query, but it could give tanstack start another edge on top of other frameworks.&lt;/p&gt;

&lt;p&gt;There would be a need for follow up features, for example it's not always as clean as &lt;code&gt;/posts/$postId&lt;/code&gt;, how do we handle more complex use cases (eg paginated queries)? I had a stab at this with a separate dependencies parameter:&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;// queries/posts/$postId.ts&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;createDataQuery&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;~/lib/createDataQuery&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;post$postIdQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createDataQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;posts/$postId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="c1"&gt;// define extra dependencies here&lt;/span&gt;
  &lt;span class="na"&gt;dependencies&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;pageSize&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;offset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&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="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;postId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/v2/posts/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?pageSize=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;offset=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;offset&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These would then be baked into the query key internally and required when using options/keys.&lt;/p&gt;

&lt;p&gt;I also think there could be some use-cases for a &lt;code&gt;queryTree.gen.ts&lt;/code&gt; although I'm not 100% what they would be yet! Anyway, if you've got this far - I appreciate the read, check out the very MVP example code here: &lt;a href="https://github.com/IainMcHugh/start-r-query" rel="noopener noreferrer"&gt;https://github.com/IainMcHugh/start-r-query&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
