Skip to content

Supastarter

This guide walks through integrating Queuebase into a Supastarter Next.js application. By the end, you’ll have background jobs, scheduled tasks, and queues running in your app with no additional infrastructure.

Before you start, make sure you have a Queuebase account. The free tier includes 10,000 job runs per month — no credit card required.

Queuebase uses a callback model: your app defines job handlers at a webhook endpoint, and Queuebase calls back to your app to execute them. Your job code lives in your app. There is no separate worker process or infrastructure to manage. Queuebase handles storage, scheduling, and retries.

Terminal window
pnpm add @queuebase/nextjs zod
pnpm add -D @queuebase/cli

The SDK provides the job router, client, and webhook handler. The CLI runs the local dev server and handles syncing schedules to production.

Zod is used in this guide, but Queuebase supports any Standard Schema-compatible validation library (Valibot, ArkType, etc.).

Create a job router with validated inputs. Each job gets a schema for type-safe input validation and a handler that runs when the job executes:

packages/jobs/src/index.ts
import { createJobRouter, job } from "@queuebase/nextjs";
import { z } from "zod";
export const jobs = createJobRouter({
sendWelcomeEmail: job({
input: z.object({
to: z.string().email(),
name: z.string(),
}),
handler: async ({ input, jobId, attempt }) => {
// your email sending logic here
console.info(`[job ${jobId}] Sending welcome email to ${input.to}`);
return { sent: true };
},
defaults: {
retries: 3,
backoff: "exponential",
},
}),
processSubscription: job({
input: z.object({
userId: z.string(),
plan: z.enum(["free", "pro", "enterprise"]),
}),
handler: async ({ input, jobId }) => {
// subscription processing logic
console.info(`[job ${jobId}] Processing ${input.plan} subscription for ${input.userId}`);
return { processed: true };
},
}),
});
export type JobRouter = typeof jobs;

The handler receives a context object with:

  • input - the validated payload (typed from your schema)
  • jobId - unique identifier for this job execution
  • attempt / maxAttempts - retry tracking
  • fail(reason) - explicitly mark the job as failed

The client gives you a type-safe way to enqueue jobs from anywhere in your app:

packages/jobs/src/client.ts
import { createClient } from "@queuebase/nextjs";
import { jobs } from "./index";
export const jobClient = createClient(jobs, {
apiUrl: process.env.QUEUEBASE_API_URL ?? "http://localhost:3847",
apiKey: process.env.QUEUEBASE_API_KEY,
callbackUrl:
process.env.QUEUEBASE_CALLBACK_URL ??
"http://localhost:3000/api/queuebase",
});
  • apiUrl — the Queuebase API. The CLI dev server runs on http://localhost:3847. In production, this is your hosted Queuebase API URL.
  • apiKey — authenticates with the production API. Not needed during local development.
  • callbackUrl — the URL Queuebase POSTs to when it’s time to execute a job.

This is the endpoint Queuebase calls to execute your jobs:

apps/web/app/api/queuebase/route.ts
import { createHandler } from "@queuebase/nextjs/handler";
import { jobs } from "@repo/jobs";
export const POST = createHandler(jobs);

Call .enqueue() from server actions, API routes, or anywhere server-side:

"use server";
import { jobClient } from "@repo/jobs/client";
export async function sendWelcomeEmail(to: string, name: string) {
const { jobId } = await jobClient.sendWelcomeEmail.enqueue({
to,
name,
});
return jobId;
}

The SDK validates the input against your schema at compile time and runtime, sends the job to the Queuebase API, and returns a jobId you can use to track status.

Scheduled jobs are defined inline using the schedule property. Scheduled jobs must have an empty input schema (z.object({})), since the scheduler has no payload to pass:

import { createJobRouter, job } from "@queuebase/nextjs";
import { z } from "zod";
export const jobs = createJobRouter({
// ... your other jobs
dailyCleanup: job({
input: z.object({}),
schedule: "every day at 2am",
handler: async ({ jobId }) => {
// cleanup expired sessions, old data, etc.
console.info(`[job ${jobId}] Running daily cleanup`);
return { cleaned: true };
},
}),
});

The schedule property accepts plain English ("every 5 minutes", "every weekday at 9am") or raw cron expressions ("0 2 * * *"). You can also pass a config object for more control:

schedule: {
cron: "every day at 2am",
timezone: "America/New_York",
overlap: "skip",
}

Schedules sync automatically when you run npx queuebase dev locally or npx queuebase sync for production.

Add these to your .env file:

Terminal window
# Development (defaults work out of the box)
QUEUEBASE_API_URL=http://localhost:3847
QUEUEBASE_CALLBACK_URL=http://localhost:3000/api/queuebase
# Production
QUEUEBASE_API_URL=https://api.queuebase.com
QUEUEBASE_API_KEY=your_api_key
QUEUEBASE_CALLBACK_URL=https://your-app.com/api/queuebase

In one terminal, start the Queuebase dev server:

Terminal window
npx queuebase dev

In another terminal, start your app:

Terminal window
pnpm dev

The dev server stores jobs in a local SQLite database and polls for pending jobs to execute. Trigger an enqueue from your app and you should see the job being received and executed in the CLI output.

  1. Create a project at queuebase.com
  2. Set the environment variables (QUEUEBASE_API_URL, QUEUEBASE_API_KEY, QUEUEBASE_CALLBACK_URL) in your hosting provider
  3. Run npx queuebase sync to sync your job types and schedules to production
  4. Deploy your app — Queuebase will start calling back to your webhook endpoint