Cron Jobs

Run scheduled work on a Cloudflare Cron Trigger.

Scheduled work runs as a Cloudflare Cron Trigger on the opb-brain-api Worker. One job is scheduled today: /cron/keep-alive, daily at 01:00 UTC.

Writing the work

A scheduled job is an ordinary route handler in apps/api. Keeping it a route rather than inline Worker code means it has one implementation, one authorization check, and can be exercised with curl like anything else.

Put new handlers under apps/api/app/cron/:

apps/api/app/cron/[your-function]/route.ts
import { database } from '@repo/database';

export const GET = async (request: Request): Promise<Response> => {
  // Authorize before doing anything — a cron endpoint is publicly reachable.

  return new Response('OK', { status: 200 });
};

The shipped keep-alive job runs SELECT 1 against D1 as a liveness probe. It used to exist to stop a serverless Postgres instance suspending between requests; D1 does not suspend, and the job was kept only because the health signal is still useful.

Scheduling

The schedule lives in the Worker config:

apps/api/wrangler.jsonc
"triggers": {
  "crons": ["0 1 * * *"]
}

How the trigger reaches the route

apps/api/worker.ts is the Worker entrypoint. It re-exports the fetch handler OpenNext generates — so HTTP behaviour is exactly what Next.js produced — and adds a scheduled handler that calls the route through that same fetch with the CRON_SECRET bearer token:

apps/api/worker.ts
async scheduled(_event, env, ctx) {
  const request = new Request('https://opb-brain-api/cron/keep-alive', {
    headers: { authorization: `Bearer ${env.CRON_SECRET}` },
  });

  const response = await openNextWorker.fetch(request, env, ctx);

  if (!response.ok) {
    throw new Error(`Keep-alive probe failed with ${response.status}.`);
  }
}

Throwing marks the scheduled invocation as failed, so it shows up in Workers observability rather than passing silently.

Authorization

The route compares the bearer token with timingSafeEqual. Without a valid token it returns 401; without CRON_SECRET configured at all it returns 503 rather than running unauthenticated. Set the secret per Worker:

Terminal
cd apps/api && bunx wrangler secret put CRON_SECRET

Triggering manually

Terminal
curl -H "authorization: Bearer $CRON_SECRET" http://localhost:3002/cron/keep-alive

Locally, wrangler dev also exposes /__scheduled to fire the trigger by hand.

On this page

GitHubEdit this page on GitHub