Getting Started
Install Eden TanStack Query and build your first type-safe query
Getting Started
Eden TanStack Query brings end-to-end type safety to your React applications using Elysia and TanStack Query.
Installation
Install the package with its peer dependencies:
bun add eden-tanstack-react-query @tanstack/react-query @elysiajs/eden elysianpm install eden-tanstack-react-query @tanstack/react-query @elysiajs/eden elysiapnpm add eden-tanstack-react-query @tanstack/react-query @elysiajs/eden elysiayarn add eden-tanstack-react-query @tanstack/react-query @elysiajs/eden elysiaThe elysia package is only needed for its type definitions. You don't need to run Elysia on the client — just import the typeof app type from your server.
Ensure your tsconfig.json has strict: true and moduleResolution set to "bundler" (or "node16" / "nodenext").
Define Your Elysia Server
Create an Elysia server with typed routes. These route definitions are what provide end-to-end type safety.
import { Elysia, t } from 'elysia'
import { cors } from '@elysiajs/cors'
const app = new Elysia()
.use(cors())
.get('/hello', () => ({ message: 'Hello from Elysia!' }))
.get('/users', () => [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
{ id: '2', name: 'Bob', email: 'bob@example.com' },
])
.get('/users/:id', ({ params }) => ({
id: params.id,
name: `User ${params.id}`,
email: `user${params.id}@example.com`,
}))
.post(
'/users',
({ body }) => ({ id: String(Date.now()), ...body }),
{
body: t.Object({
name: t.String(),
email: t.String(),
}),
}
)
.listen(3000)
// This type export is what enables type inference on the client
export type App = typeof appThe export type App = typeof app line is crucial — it's what makes the entire type-safe chain work.
Create Eden Instance
Set up the Eden TanStack Query instance and treaty client.
import { createEdenTanStackQuery } from 'eden-tanstack-react-query'
import { treaty } from '@elysiajs/eden'
import type { App } from '../../server'
export const { EdenProvider, useEden } = createEdenTanStackQuery<App>()
export const edenClient = treaty<App>('http://localhost:3000')Set Up Providers
Wrap your app with QueryClientProvider and EdenProvider.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { EdenProvider, edenClient } from './lib/eden'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<EdenProvider client={edenClient} queryClient={queryClient}>
<YourApp />
</EdenProvider>
</QueryClientProvider>
)
}Use in Components
Query
import { useQuery } from '@tanstack/react-query'
import { useEden } from '../lib/eden'
export function UserList() {
const eden = useEden()
const { data: users, isLoading } = useQuery(
eden.users.get.queryOptions()
)
if (isLoading) return <div>Loading...</div>
return (
<ul>
{users?.map((user) => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
)
}Path Parameters
For routes like /users/:id, call the path segment as a function:
const { data: user } = useQuery(
eden.users({ id: userId }).get.queryOptions()
)Mutation
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useEden } from '../lib/eden'
export function CreateUser() {
const eden = useEden()
const queryClient = useQueryClient()
const createUser = useMutation({
...eden.users.post.mutationOptions(),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: eden.users.get.queryKey(),
})
},
})
return (
<button
onClick={() => createUser.mutate({ name: 'Alice', email: 'alice@example.com' })}
disabled={createUser.isPending}
>
Create User
</button>
)
}Monorepo Setup
If your Elysia server and React client live in separate packages, export the app type from your server package and import it in the client:
export type App = typeof appimport type { App } from '@myorg/server'Next Steps
- Query Options — All available methods and options
- Infinite Queries — Pagination with
infiniteQueryOptions() - Query Keys — How query keys are generated and used