QUESTPIE
Build Your BackendBusiness Logic

Jobs

Background tasks with typed payloads — send emails, process data, schedule work.

Jobs are background tasks that run outside the request lifecycle. They're ideal for sending emails, processing data, generating reports, or any work that shouldn't block an API response.

Defining a Job

jobs/send-appointment-confirmation.ts
import { job } from "questpie";
import z from "zod";

export default job({
	name: "sendAppointmentConfirmation",
	schema: z.object({
		appointmentId: z.string(),
		customerId: z.string(),
	}),
	handler: async ({ payload, email, collections }) => {
		const customer = await collections.user.findOne({
			where: { id: payload.customerId },
		});
		if (!customer) return;

		const appointment = await collections.appointments.findOne({
			where: { id: payload.appointmentId },
			with: { barber: true, service: true },
		});
		if (!appointment) return;

		await email.sendTemplate({
			template: "appointmentConfirmation",
			input: {
				customerName: customer.name,
				appointmentId: appointment.id,
				barberName: appointment.barber.name,
				serviceName: appointment.service.name,
				scheduledAt: appointment.scheduledAt.toISOString(),
			},
			to: customer.email,
		});
	},
});

Place job files in jobs/. The filename becomes the job key: send-appointment-confirmation.tssendAppointmentConfirmation.

Publishing Jobs

Publish jobs from hooks, routes, or other jobs via the queue context:

collections/appointments.ts
.hooks({
  afterChange: async ({ data, operation, queue }) => {
    if (operation === "create") {
      await queue.sendAppointmentConfirmation.publish({
        appointmentId: data.id,
        customerId: data.customer,
      });
    }
  },
})

The queue object is fully typed — autocompletion shows all available jobs and their expected payloads.

Handler Context

Job handlers receive the same AppContext as routes:

PropertyDescription
payloadValidated data matching the Zod schema
collectionsTyped collection API
emailEmail service
queuePublish other jobs
dbDatabase instance

Email Templates

Jobs often send emails using templates defined in emails/:

emails/appointment-confirmation.ts
import { email } from "questpie";
import z from "zod";

export default email({
	name: "appointmentConfirmation",
	schema: z.object({
		customerName: z.string(),
		appointmentId: z.string(),
		barberName: z.string(),
		serviceName: z.string(),
		scheduledAt: z.string(),
	}),
	handler: ({ input }) => ({
		subject: "Appointment Confirmed",
		html: `
      <h1>Hi ${input.customerName}!</h1>
      <p>Your appointment has been confirmed.</p>
      <table>
        <tr><td>Barber</td><td>${input.barberName}</td></tr>
        <tr><td>Service</td><td>${input.serviceName}</td></tr>
        <tr><td>When</td><td>${input.scheduledAt}</td></tr>
      </table>
    `,
	}),
});

Queue Adapter

Configure the queue adapter in your runtime config. QUESTPIE uses pg-boss by default:

questpie.config.ts
import { pgBossAdapter, runtimeConfig } from "questpie";

export default runtimeConfig({
	queue: {
		adapter: pgBossAdapter({
			connectionString: process.env.DATABASE_URL,
		}),
	},
	// ...
});

Real-World Example

Notifying subscribers when a blog post is published:

jobs/notify-blog-subscribers.ts
import { job } from "questpie";
import z from "zod";

export default job({
	name: "notifyBlogSubscribers",
	schema: z.object({
		postId: z.string(),
		title: z.string(),
		excerpt: z.string(),
		slug: z.string(),
	}),
	handler: async ({ payload, email, collections }) => {
		const users = await collections.user.find({ limit: 1000 });

		await Promise.allSettled(
			users.docs.map((user) =>
				email.sendTemplate({
					template: "newBlogPost",
					input: {
						title: payload.title,
						excerpt: payload.excerpt,
						slug: payload.slug,
					},
					to: user.email,
				}),
			),
		);
	},
});
  • Hooks — Trigger jobs from collection hooks
  • Routes — Server-side logic
  • Queue — Queue infrastructure setup
  • Email — Email adapter configuration

On this page