QUESTPIE
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

On this page