Eden TanStack Query

Introduction

Type-safe TanStack Query integration for Elysia Eden

Eden TanStack Query

Type-safe TanStack Query integration for Elysia Eden. Like @trpc/react-query, but for Elysia.

Features

  • End-to-end type safety - Full TypeScript inference from Elysia routes
  • Native TanStack Query patterns - Use standard useQuery, useMutation, useInfiniteQuery
  • Query options factories - queryOptions(), mutationOptions(), infiniteQueryOptions()
  • Automatic query key generation - Type-safe keys derived from route paths
  • Path parameter support - eden.users({ id: '1' }).get.queryOptions()
  • Query invalidation helpers - queryFilter() for cache management

Installation

npm i eden-tanstack-react-query @tanstack/react-query @elysiajs/eden elysia

Quick Start

1. Define your Elysia server

server.ts
import { Elysia, t } from 'elysia'

const posts = [
  { id: 1, title: 'First post' },
  { id: 2, title: 'Second post' },
  { id: 3, title: 'Third post' }
]

const app = new Elysia()
  .get('/users', () => [
    { id: '1', name: 'Alice' },
    { id: '2', name: 'Bob' }
  ])
  .get('/users/:id', ({ params }) => ({
    id: params.id,
    name: `User ${params.id}`
  }))
  .post('/users', ({ body }) => ({
    id: String(Date.now()),
    ...body
  }), {
    body: t.Object({ name: t.String() })
  })
  .get('/posts', ({ query }) => {
    const cursor = query.cursor ?? 0
    const limit = query.limit ?? 10
    const items = posts.slice(cursor, cursor + limit)
    const nextCursor = cursor + items.length < posts.length
      ? cursor + items.length
      : null

    return { items, nextCursor }
  }, {
    query: t.Object({
      cursor: t.Optional(t.Numeric({ minimum: 0, multipleOf: 1 })),
      limit: t.Optional(t.Numeric({ minimum: 1, multipleOf: 1 }))
    })
  })
  .listen(3000)

export type App = typeof app

2. Create typed hooks

lib/eden.ts
import { createEdenTanStackQuery } from 'eden-tanstack-react-query'
import { treaty } from '@elysiajs/eden'
import type { App } from '../server'

export const { EdenProvider, useEden, useEdenClient } = createEdenTanStackQuery<App>()
export const edenClient = treaty<App>('http://localhost:3000')

3. Set up providers

App.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { EdenProvider, edenClient } from './lib/eden'

const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <EdenProvider client={edenClient} queryClient={queryClient}>
        <YourApp />
      </EdenProvider>
    </QueryClientProvider>
  )
}

4. Use in components

UserList.tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useEden } from './lib/eden'

function UserList() {
  const eden = useEden()
  const queryClient = useQueryClient()

  // Query
  const { data: users } = useQuery(eden.users.get.queryOptions())

  // Query with path params
  const { data: user } = useQuery(
    eden.users({ id: '1' }).get.queryOptions()
  )

  // Mutation with cache invalidation
  const createUser = useMutation({
    ...eden.users.post.mutationOptions(),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: eden.users.get.queryKey() })
    }
  })

  return (
    <div>
      <ul>
        {users?.map(user => <li key={user.id}>{user.name}</li>)}
      </ul>
      <button onClick={() => createUser.mutate({ name: 'New User' })}>
        Add User
      </button>
    </div>
  )
}

API Reference

createEdenTanStackQuery

Creates typed providers and hooks for your Elysia app.

const { EdenProvider, useEden, useEdenClient } = createEdenTanStackQuery<App>()

useEden

Returns the Eden options proxy with methods for each route.

Query Methods (GET, HEAD, OPTIONS)

MethodDescription
.queryOptions(input, opts?)Options for useQuery; input is optional only when the route query is omittable
.queryKey(input?)Query key for cache operations
.infiniteQueryOptions(input, opts)Options for useInfiniteQuery; available for supported non-union cursor routes

Mutation Methods (POST, PUT, PATCH, DELETE)

MethodDescription
.mutationOptions(opts?)Options for useMutation
.mutationKey()Mutation key

Path Parameters

Access routes with path params using function calls:

// Route: /users/:id
eden.users({ id: '123' }).get.queryOptions()

// Nested: /posts/:postId/comments/:commentId
eden.posts({ postId: '1' }).comments({ commentId: '2' }).get.queryOptions()

Query with Input

Pass query parameters or request body:

// Query params: GET /users?role=admin
eden.users.get.queryOptions({ role: 'admin' })

// With TanStack Query options
eden.users.get.queryOptions(
  { role: 'admin' },
  { staleTime: 5 * 60 * 1000 }
)

Infinite Queries

For paginated data. Optional cursor fields default to null; required cursor fields need an explicit non-null initialCursor. Union query schemas do not expose infinite-query methods because input and cursor variants cannot be correlated safely.

import { useInfiniteQuery } from '@tanstack/react-query'

const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
  eden.posts.get.infiniteQueryOptions(
    { limit: 10 },
    {
      getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
      initialCursor: null,
    }
  )
)

Comparison

Featureeden-tanstack-react-query@ap0nia/eden-react-query
API StyleuseQuery(eden.users.get.queryOptions())eden.users.get.useQuery() + useUtils()
TanStack Query Native✅ Standard hooks only⚠️ Wrapper hooks
Query KeysExplicit queryKey() / queryFilter()tRPC-style utils
Learning CurveStandard TanStack QuerytRPC-style API

On this page