T
TanStack9mo ago
rival-black

TanStack Start + Query + db access example

Does anyone have a simple CRUD example app that uses Start and Query with db access? I'm porting a side project from NextJS which uses these + Drizzle and trying to find a tidy way to structure my query functions => server functions => Drizzle functions for best readability and reusability
3 Replies
eastern-cyan
eastern-cyan9mo ago
I can't share the repository I'm working on but are you more interested in the architecture or the setup (e.g. how things are wired up)?
rival-black
rival-blackOP9mo ago
I'm trying to get things cleanly organised to avoid any accidental direct calling of server code from client code (mostly due to copying across NextJS server actions into the app). What I've come up with for now is -
// /routes/contacts.tsx
export const Route = createFileRoute('/contacts')({
component: ContactsPage,
loader: ({ context }) => {
context.queryClient.prefetchQuery(contactsQueryOptions())
},
ssr: false,
})

function ContactsPage() {
const contacts = useContacts()
return ( ... )
}
// /routes/contacts.tsx
export const Route = createFileRoute('/contacts')({
component: ContactsPage,
loader: ({ context }) => {
context.queryClient.prefetchQuery(contactsQueryOptions())
},
ssr: false,
})

function ContactsPage() {
const contacts = useContacts()
return ( ... )
}
// /components/contacts/useContacts.tsx
export const contactsQueryOptions = () =>
queryOptions({
queryKey: ['contacts'],
queryFn: async () => await getContactsSF(),
})

export default function useContacts() {
const { data: contact } = useQuery(contactsQueryOptions())
return contact
}
// /components/contacts/useContacts.tsx
export const contactsQueryOptions = () =>
queryOptions({
queryKey: ['contacts'],
queryFn: async () => await getContactsSF(),
})

export default function useContacts() {
const { data: contact } = useQuery(contactsQueryOptions())
return contact
}
// /lib/server/contactsServer.ts
export const getContactSF = createServerFn({ method: 'GET' })
.validator((data: number) => data)
.handler(async (ctx) => getContact(ctx.data))

export const getContactSF = createServerFn({ method: 'GET' })
.validator((data: number) => data)
.handler(async (ctx) => getContact(ctx.data))

export const addContactSF = createServerFn({ method: 'POST' })
.validator((data: NewContact) => data)
.handler(async ({ data }) => await addContact(data))
// /lib/server/contactsServer.ts
export const getContactSF = createServerFn({ method: 'GET' })
.validator((data: number) => data)
.handler(async (ctx) => getContact(ctx.data))

export const getContactSF = createServerFn({ method: 'GET' })
.validator((data: number) => data)
.handler(async (ctx) => getContact(ctx.data))

export const addContactSF = createServerFn({ method: 'POST' })
.validator((data: NewContact) => data)
.handler(async ({ data }) => await addContact(data))
// /lib/db/contacts.ts
export async function getContact(contactId: number) {
const result = await db.query.contacts.findFirst({
where: (contacts, { eq }) => eq(contacts.id, contactId),
})
return result
}

export async function addContact(contact: NewContact) {
const result = await db.insert(contacts).values([contact])
return result.changes
}
// /lib/db/contacts.ts
export async function getContact(contactId: number) {
const result = await db.query.contacts.findFirst({
where: (contacts, { eq }) => eq(contacts.id, contactId),
})
return result
}

export async function addContact(contact: NewContact) {
const result = await db.insert(contacts).values([contact])
return result.changes
}
eastern-cyan
eastern-cyan9mo ago
I have the exact same structure; the only difference is that I use ensureQueryData in the loader instead of prefetchQuery

Did you find this page helpful?