#TanstackQuery's storage persistance with convex

21 messages · Page 1 of 1 (latest)

torn monolith
#

Can you give me an example setup of using TanstackQuery's storage persistance with convex?

deft bloomBOT
#

Hi @torn monolith. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

I'm sorry, but the knowledge sources do not contain explicit information or an example setup for using TanStack Query's storage persistence (such as persisting the query cache to localStorage or IndexedDB) specifically with Convex. The available documentation covers how to integrate Convex with TanStack Query for live-updating queries and how to set up the ConvexQueryClient with TanStack Query's QueryClient, but it does not detail storage persistence configuration or provide an example of persisting the query cache.

If you are looking for general integration between Convex and TanStack Query, here is a basic setup for live-updating queries:

import { ConvexQueryClient } from "@convex-dev/react-query";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import ReactDOM from "react-dom/client";
import App from "./App";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
const convexQueryClient = new ConvexQueryClient(convex);
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      queryKeyHashFn: convexQueryClient.hashFn(),
      queryFn: convexQueryClient.queryFn(),
    },
  },
});
convexQueryClient.connect(queryClient);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <ConvexProvider client={convex}>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </ConvexProvider>,
);

This setup enables live-updating queries with Convex and TanStack Query, but does not include storage persistence for the query cache. For more details, see the Convex with TanStack Query documentation.

#

If you need storage persistence, you would typically use TanStack Query's Persist Query Client plugin, but there is no example or official guidance in the Convex documentation about using this with ConvexQueryClient. Please consult the TanStack Query documentation for general usage, and consider testing compatibility in your project.

If you need further help, you may want to ask in the Convex community or on Discord.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@torn monolith Kapa.ai is still learning and improving, please let me know how I did by reacting below.

torn monolith
#

separate question from previous question.
How do I do a fetch request to an api from inside convex? @deft bloom

deft bloomBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

To make a fetch request to an external API from inside Convex, you need to use an action (not a query or mutation), because only actions are allowed to make external HTTP requests. Here’s a basic example of how to do this:

import { action } from "./_generated/server";

export const fetchFromApi = action({
  args: { url: v.string() },
  handler: async (ctx, args) => {
    const response = await fetch(args.url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    return data;
  },
});

Key points:

  • Use the action (or internalAction) constructor.
  • The fetch function is available inside actions.
  • Queries and mutations cannot make external HTTP requests; only actions can do this due to Convex’s determinism guarantees.

For more details, see the Convex documentation on actions and fetch and tutorial example.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@torn monolith Kapa.ai is still learning and improving, please let me know how I did by reacting below.

torn monolith
#

so say I want to make a mutation to create an order and save it to db, and then create a post request to an a different system how would thatm utation look like?

#

@deft bloom

deft bloomBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Sorry @torn monolith, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist:

Perhaps try again in 15mins?

torn monolith
#

@deft bloom so say I want to make a mutation to create an order and save it to db, and then create a post request to an a different system how would thatm utation look like?

deft bloomBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

To achieve this in Convex, you should separate the database write (mutation) from the external POST request (action). Mutations in Convex are transactional and cannot make external network requests. The recommended pattern is:

  1. Mutation: Create the order and save it to the database.
  2. Action: Triggered after the mutation commits, makes the POST request to the external system.

You can schedule the action from within the mutation using ctx.scheduler.runAfter. Here’s how this pattern looks, based on the knowledge sources:

// convex/orders.ts

import { v } from "convex/values";
import { mutation, internalAction } from "./_generated/server";
import { internal } from "./_generated/api";

// Mutation: Create the order and schedule the action
export const createOrder = mutation({
  args: { orderData: v.any() },
  handler: async (ctx, { orderData }) => {
    const orderId = await ctx.db.insert("orders", orderData);
    // Schedule the action to run after the mutation commits
    await ctx.scheduler.runAfter(0, internal.orders.notifyExternalSystem, { orderId });
    return orderId;
  },
});

// Action: Make the POST request to the external system
export const notifyExternalSystem = internalAction({
  args: { orderId: v.id("orders") },
  handler: async (ctx, { orderId }) => {
    // Optionally, fetch order details if needed
    const order = await ctx.runQuery(internal.orders.getOrder, { orderId });
    const response = await fetch("<https://external.api/endpoint",> {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(order),
    });
    if (!response.ok) {
      throw new Error("Failed to notify external system");
    }
  },
});
#

// (Optional) Internal query to fetch order details
export const getOrder = internalQuery({
args: { orderId: v.id("orders") },
handler: async (ctx, { orderId }) => {
return await ctx.db.get(orderId);
},
});


**Summary:**  
- The mutation writes to the database and schedules the action.
- The action, which runs after the mutation commits, performs the external POST request.
- This ensures transactional integrity and follows Convex best practices for interacting with external systems.

References:  
- [Convex Tutorial: Hooking it up to your app](<https://docs.convex.dev/tutorial/actions#hooking-it-up-to-your-app>)
- [Using Pinecone and Embeddings: Kick off a background action](<https://stack.convex.dev/pinecone-and-embeddings#kick-off-a-background-action>)
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
#

@torn monolith Kapa.ai is still learning and improving, please let me know how I did by reacting below.

torn monolith
#

@deft bloom in your example above whats the cost for that? how many function calls does it run?

deft bloomBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

In the example above, creating an order and notifying an external system would result in two function calls:

  1. The mutation (createOrder) that writes the order to the database and schedules the action.
  2. The action (notifyExternalSystem) that is scheduled and then runs to make the POST request to the external system.

Each of these counts as a separate function call for billing purposes. So, for each order created, you would be billed for two function calls.

This pattern is consistent with Convex’s function call accounting, where each invocation of a mutation, action, or query is counted as a function call, including those scheduled via ctx.scheduler.runAfter see discussion here.

If the action itself calls additional queries or mutations (e.g., to fetch order details), each of those would also count as additional function calls.

Summary:

  • 1 function call for the mutation
  • 1 function call for the action
  • (plus any additional function calls made within the action, such as internal queries or mutations)

If you want to track the exact number of function calls and their resource usage, you can use the log stream’s function_execution events.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: