What is QUESTPIE
A backend-first TypeScript framework where schema drives everything — from database to typed frontends.
QUESTPIE is a backend-first TypeScript framework. You define your data schema once, codegen generates the typed runtime, and any frontend can consume it through introspection.
It is not a CMS. The admin panel is one projection of your schema — the same way an SDK, an OpenAPI spec, or a mobile app would be. The framework exists independently of any UI.
The Core Idea
Schema Definition → Codegen → Typed Runtime → Projection → Frontend(s)
↓
Database tables
API validation
Query operators
Type inferenceYou write a collection file. Codegen reads it and generates:
- Database schema — Drizzle ORM table definitions
- API layer — CRUD endpoints with Zod validation
- Type exports -
AppConfig,AppCollections,AppRoutesfor end-to-end type safety - Module augmentation —
AppContextwith typedcollections,queue,email, and any custom services
The admin panel, client SDK, and OpenAPI spec all consume these generated types. Nothing is defined twice.
Mental Model
Think of QUESTPIE in three layers:
Layer 1: Schema (you write this)
import { collection } from "#questpie/factories";
export default collection("posts").fields(({ f }) => ({
title: f.text().required(),
body: f.textarea(),
status: f.select(["draft", "published"]),
}));Layer 2: Codegen (automatic)
Running questpie generate discovers your files and generates:
- Type-safe
appinstance with all collections, routes, jobs, services AppContextmodule augmentation so every handler gets typed DIAppConfigtype for the client SDK
Layer 3: Projection (consumers)
const posts = await client.collections.posts.find({
where: { status: "published" },
orderBy: { createdAt: "desc" },
});
// posts is fully typed from your schema// Reads the same schema via introspection
// Generates list views, form fields, filters automaticallyWhy Not Just Use [X]?
| If you need... | QUESTPIE gives you... |
|---|---|
| A typed API from schema | Collections → CRUD endpoints + Zod validation + query operators |
| Business logic alongside data | Routes, hooks, jobs - all in the same project with DI |
| An admin panel | @questpie/admin reads your schema, no separate config |
| Multiple frontends | One schema, typed client SDK for any framework |
| Full-stack type safety | Schema types flow from server → codegen → client |
Context-First Architecture
Every handler in QUESTPIE receives its dependencies through context — no imports, no singletons, no god objects:
import { route } from "questpie";
import z from "zod";
export default route()
.post()
.schema(
z.object({
barberId: z.string(),
serviceId: z.string(),
scheduledAt: z.string().datetime(),
}),
)
.handler(async ({ input, collections, queue }) => {
const service = await collections.services.findOne({
where: { id: input.serviceId },
});
const appointment = await collections.appointments.create({
barber: input.barberId,
service: input.serviceId,
scheduledAt: new Date(input.scheduledAt),
status: "pending",
});
await queue.sendAppointmentConfirmation.publish({
appointmentId: appointment.id,
});
return { success: true, appointmentId: appointment.id };
});collections, queue, email, db, session — all injected through AppContext. Codegen generates the type augmentation, so everything is discoverable and typed.
What's Next
- Installation — Get QUESTPIE running
- First App — Build a working API in 5 minutes
- Project Structure — File conventions and discovery rules