Build Your BackendBusiness Logic
Routes
Typed server routes — define input schema, handler logic, and call from the client.
Routes are typed server-side logic exposed as endpoints. Define an input schema with Zod, write a handler that receives typed context, and call it from the client with full type safety.
On the client, you call client.routes.routeName().
Defining a Route
routes/get-active-barbers.ts
import { route } from "questpie";
import z from "zod";
export default route()
.post()
.schema(z.object({}))
.handler(async ({ collections }) => {
return await collections.barbers.find({
where: { isActive: true },
});
});Place route files in routes/. The filename becomes the route key: get-active-barbers.ts → getActiveBarbers.
Input Validation
Routes validate input with Zod:
routes/create-booking.ts
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(),
customerName: z.string().min(2),
customerEmail: z.string().email(),
customerPhone: z.string().optional(),
notes: z.string().optional(),
}),
)
.handler(async ({ input, collections }) => {
const service = await collections.services.findOne({
where: { id: input.serviceId },
});
if (!service) throw new Error("Service not found");
const appointment = await collections.appointments.create({
barber: input.barberId,
service: input.serviceId,
scheduledAt: new Date(input.scheduledAt),
status: "pending",
notes: input.notes || null,
});
return {
success: true,
appointmentId: appointment.id,
message: "Appointment booked successfully!",
};
});Handler Context
Route handlers receive the full AppContext:
handler: async ({ input, collections, queue, email, db, session, blog }) => {
// input — validated data matching the Zod schema
// collections — typed collection API
// queue — publish background jobs
// email — send emails
// db — raw database access
// session — current auth session
// blog — custom service from services/blog.ts
};Calling Routes
From the client SDK
import { client } from "@/lib/client";
// Fully typed — input and return type inferred from the route definition
const result = await client.routes.createBooking({
barberId: "abc",
serviceId: "def",
scheduledAt: "2025-03-15T10:00:00Z",
customerName: "John",
customerEmail: "john@example.com",
});Via HTTP
Routes are accessible at flat URLs under your basePath (/api by default):
curl -X POST http://localhost:3000/api/create-booking \
-H "Content-Type: application/json" \
-d '{"barberId": "abc", "serviceId": "def", ...}'Nested Routes
Organize routes in subdirectories for namespacing:
routes/
├── booking/
│ ├── create.ts → client.routes.booking.create()
│ └── cancel.ts → client.routes.booking.cancel()
├── get-active-barbers.ts → client.routes.getActiveBarbers()
└── get-stats.ts → client.routes.getStats()Real-World Example
Computing available time slots with business logic:
routes/get-available-time-slots.ts
import { route } from "questpie";
import z from "zod";
export default route()
.post()
.schema(
z.object({
date: z.string(),
barberId: z.string(),
serviceId: z.string(),
}),
)
.handler(async ({ input, collections }) => {
const barber = await collections.barbers.findOne({
where: { id: input.barberId, isActive: true },
});
if (!barber) throw new Error("Barber not found or inactive");
const service = await collections.services.findOne({
where: { id: input.serviceId, isActive: true },
});
if (!service) throw new Error("Service not found or inactive");
// Get existing appointments for the day
const startOfDay = new Date(input.date);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(input.date);
endOfDay.setHours(23, 59, 59, 999);
const existing = await collections.appointments.find({
where: {
barber: input.barberId,
scheduledAt: { gte: startOfDay, lte: endOfDay },
},
});
// Generate available slots (business logic)
const slots = generateAvailableSlots(
barber.workingHours,
existing.docs,
service.duration,
input.date,
);
return slots;
});Related Pages
- Jobs — Background tasks
- Routes API — Route builder API reference
- Hooks — Collection lifecycle hooks
- TanStack Query — Client-side data fetching