Data Fetching
Server-side data fetching with complete type safety from database to component.
#Server-Side Fetching
All data fetching happens on the server in your route handlers. Data is passed to components as type-safe props.
No Client Waterfalls
Data is fetched before rendering. No loading states for initial page load.
Secure by Default
Database queries run on the server. Credentials never reach the client.
// Data fetching happens on the server in your route handlers
// The data is passed to components as props: fully typed
.get('/posts', async () => {
// Fetch data on the server
const posts = await db.query.posts.findMany({
with: { author: true }
});
// TypeScript knows posts is Post[] with author relation
return handleReactPageRequest({
Page: PostList,
index: asset(manifest, 'PostListIndex'),
props: { posts } // Type-safe: PostListProps['posts'] must match
});
})#Eden Treaty
For client-side data fetching, AbsoluteJS uses Eden Treaty instead of raw fetch requests. Eden provides end-to-end type safety by deriving types directly from your Elysia server.
Route Validation
TypeScript errors if you try to call a route that doesn't exist on your server.
Request Body Typing
POST/PUT bodies are validated at compile time against your endpoint's schema.
Server Setup
Export the server type from your Elysia app. This is what Eden uses to infer all routes and their types:
// src/backend/server.ts
const app = new Elysia()
.get('/api/posts', async () => {
const posts = await db.query.posts.findMany();
return posts;
})
.get('/api/posts/:id', async ({ params }) => {
return db.query.posts.findFirst({
where: eq(posts.id, Number(params.id))
});
})
.post('/api/posts', async ({ body }) => {
return db.insert(posts).values(body).returning();
}, {
body: t.Object({
title: t.String(),
content: t.String()
})
});
// Export the type for Eden Treaty
export type Server = typeof app;Client Setup
Create a treaty client using your server type:
// src/frontend/eden/treaty.ts
import { treaty } from '@elysiajs/eden';
import type { Server } from '../../backend/server';
const serverUrl =
typeof window !== 'undefined'
? window.location.origin
: 'http://localhost:3000';
// The Server type comes from your Elysia server export
export const server = treaty<Server>(serverUrl);Usage
Call your API endpoints with full type safety. Routes, params, and request bodies are all validated at compile time:
// Client-side usage: fully type-safe!
import { server } from '../eden/treaty';
// TypeScript knows this route exists and returns Post[]
const { data: posts, error } = await server.api.posts.get();
// error is typed based on your server's status responses
if (error) {
// error.value contains the error message from the server
console.error(error.value);
return;
}
// Route params are type-checked
const { data: post } = await server.api.posts({ id: '123' }).get();
// Request body is validated at compile time
// TypeScript error if title or content is missing!
const { data: newPost } = await server.api.posts.post({
title: 'Hello World',
content: 'My first post'
});
// This would be a TypeScript error: route doesn't exist:
// server.api.nonexistent.get() ❌Compile-Time Safety
Eden Treaty catches API mistakes before your code runs:
// Eden Treaty prevents common API mistakes at compile time
// ❌ TypeScript Error: Property 'users' does not exist
server.api.users.get();
// ❌ TypeScript Error: Property 'name' is missing
server.api.posts.post({ title: 'Hello' });
// ❌ TypeScript Error: Argument of type 'number' is not assignable
server.api.posts({ id: 123 }).get(); // id must be string
// ✅ All correct: TypeScript is happy
const { data, error } = await server.api.posts.get();
if (error) {
console.error(error);
return;
}
// data is typed as Post[]
console.log(data);#End-to-End Type Flow
Types flow seamlessly from your database schema through your route handlers to your components. TypeScript catches mismatches at compile time.
End-to-End Type Safety
SERVER
Schema
Define your tables
pgTable('users', { id, name, email })Drizzle ORM
Infer TypeScript types
type User = typeof users.$inferSelectRoute Handler
Query and return data
db.select().from(users)CLIENT
Eden Treaty
Type-safe API calls
const { data, error } = api.users.get()Component
Fully typed props
type Props = { user: User }TypeScript validates at every step: errors caught at compile time, not runtime
// Types flow from database schema to components
// 1. Database schema (Drizzle)
const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
content: text('content').notNull(),
authorId: integer('author_id').references(() => users.id)
});
// 2. Inferred types
type Post = typeof posts.$inferSelect;
// { id: number; title: string; content: string; authorId: number | null }
// 3. Props type
type PostPageProps = {
post: Post;
author: User;
};
// 4. Component receives correctly typed data
export const PostPage = ({ post, author }: PostPageProps) => (
<article>
<h1>{post.title}</h1>
<p>By {author.name}</p>
<div>{post.content}</div>
</article>
);#Type-Safe Status Responses
Status responses are also type-safe. Elysia's status() function returns typed responses for any HTTP status code:
// Status responses are also type-safe with Elysia
.get('/posts/:id', async ({ params, status }) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, Number(params.id))
});
// Return status with code and message string
if (!post) {
return status(404, 'Post not found');
}
return handleReactPageRequest({
Page: PostPage,
index: indexPath,
props: { post }
});
}, {
params: t.Object({ id: t.String() })
})