Integrate Frontend
Type Inference
End-to-end type safety — from schema definition to client SDK.
QUESTPIE provides end-to-end TypeScript type safety. Types flow from your field definitions through codegen to the client SDK. No manual type definitions needed.
The Type Flow
Field Definition Codegen Client SDK
f.text().required() → AppCollections type → client.collections.posts.find()
f.number() → with field types → where: { price: { gte: 1000 } }
f.select([...]) → and operators → data.status === "published"Generated Types
Codegen produces:
// From .generated/
export type AppConfig = {
collections: {
posts: {
select: {
id: string;
title: string;
body: string | null;
status: "draft" | "published";
};
insert: {
title: string;
body?: string | null;
status?: "draft" | "published";
};
where: {
title?: string | { contains?: string };
status?: "draft" | "published" | { in?: string[] };
};
orderBy: { title?: "asc" | "desc"; createdAt?: "asc" | "desc" };
};
// ...
};
};Client Autocompletion
The client uses AppConfig which includes collections, globals, and routes:
const client = createClient<AppConfig>({
baseURL: "http://localhost:3000",
basePath: "/api",
});
// Autocomplete: collections.posts, collections.barbers, ...
// Autocomplete: where.status, where.title, ...
// Autocomplete: routes.createBooking, routes.getActiveBarbers, ...
// Autocomplete: routes["health"], routes["webhooks/stripe"], ...Server Context Types
Codegen augments AppContext so handlers get typed DI:
// In any handler — everything is typed
handler: async ({ input, collections, queue, email, blog }) => {
const post = await collections.posts.findOne({
where: { id: input.postId }, // typed
});
// post.title — string
// post.status — "draft" | "published"
await queue.notifyBlogSubscribers.publish({
postId: post.id, // typed payload
title: post.title,
});
};Route Return Types
Route return types are inferred from the handler:
// Server
export default route()
.post()
.schema(z.object({ barberId: z.string() }))
.handler(async ({ input, collections }) => {
return await collections.barbers.findOne({
where: { id: input.barberId },
});
});
// Client — return type is inferred
const barber = await client.routes.getBarber({ barberId: "abc" });
// barber is typed as the barber record