| summary | Export a D1 database into R2 storage with Workflows | ||
|---|---|---|---|
| products |
|
||
| tags |
|
||
| pcx_content_type | example | ||
| title | Export and save D1 database | ||
| sidebar |
|
||
| description | Send invoice when shopping cart is checked out and paid for | ||
| reviewed | 2024-12-30 |
import { WranglerConfig } from "~/components";
In this example, we implement a Workflow that runs on a schedule using the schedules field on the Workflow binding. That Workflow initiates a backup for a D1 database using the REST API, and then stores the SQL dump in an R2 bucket.
When the Workflow is triggered, it fetches the REST API to initiate an export job for a specific database. Then it fetches the same endpoint to check if the backup job is ready and the SQL dump is available to download.
As shown in this example, Workflows handles both the responses and failures, thereby removing the burden from the developer. Workflows retries the following steps:
- API calls until it gets a successful response
- Fetching the backup from the URL provided
- Saving the file to R2
The Workflow can run until the backup file is ready, handling all of the possible conditions until it is completed.
This example provides simplified steps for backing up a D1 database to help you understand the possibilities of Workflows. In every step, it uses the default sleeping and retrying configuration. In a real-world scenario, more steps and additional logic would likely be needed.
import {
WorkflowEntrypoint,
WorkflowStep,
WorkflowEvent,
} from "cloudflare:workers";
// We are using R2 to store the D1 backup
type Env = {
BACKUP_WORKFLOW: Workflow;
D1_REST_API_TOKEN: string;
BACKUP_BUCKET: R2Bucket;
ACCOUNT_ID: string;
DATABASE_ID: string;
};
// Workflow logic
export class backupWorkflow extends WorkflowEntrypoint<Env> {
async run(_event: WorkflowEvent<unknown>, step: WorkflowStep) {
const accountId = this.env.ACCOUNT_ID;
const databaseId = this.env.DATABASE_ID;
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/d1/database/${databaseId}/export`;
const method = "POST";
const headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("Authorization", `Bearer ${this.env.D1_REST_API_TOKEN}`);
const bookmark = await step.do(
`Starting backup for ${databaseId}`,
async () => {
const payload = { output_format: "polling" };
const res = await fetch(url, {
method,
headers,
body: JSON.stringify(payload),
});
const { result } = (await res.json()) as any;
// If we don't get `at_bookmark` we throw to retry the step
if (!result?.at_bookmark) throw new Error("Missing `at_bookmark`");
return result.at_bookmark;
},
);
await step.do("Check backup status and store it on R2", async () => {
const payload = { current_bookmark: bookmark };
const res = await fetch(url, {
method,
headers,
body: JSON.stringify(payload),
});
const { result } = (await res.json()) as any;
// The endpoint sends `signed_url` when the backup is ready to download.
// If we don't get `signed_url` we throw to retry the step.
if (!result?.signed_url) throw new Error("Missing `signed_url`");
const dumpResponse = await fetch(result.signed_url);
if (!dumpResponse.ok) throw new Error("Failed to fetch dump file");
// Finally, stream the file directly to R2
await this.env.BACKUP_BUCKET.put(result.filename, dumpResponse.body);
});
}
}
export default {
async fetch(req: Request, env: Env): Promise<Response> {
return new Response("Not found", { status: 404 });
},
};Here is a minimal package.json:
{
"devDependencies": {
"wrangler": "^3.99.0"
}
}Create D1_REST_API_TOKEN as a secret with permission to export the target D1 database.
Here is a Wrangler configuration file:
Each scheduled run creates a new Workflow instance automatically.
Use the latest Wrangler release when configuring Workflow schedules. If your local Wrangler schema does not recognize schedules yet, update Wrangler before deploying.
{ "$schema": "./node_modules/wrangler/config-schema.json", "name": "backup-d1", "main": "src/index.ts", "compatibility_date": "$today", "compatibility_flags": [ "nodejs_compat" ], "vars": { "ACCOUNT_ID": "account-id", "DATABASE_ID": "database-id" }, "workflows": [ { "name": "backup-workflow", "binding": "BACKUP_WORKFLOW", "class_name": "backupWorkflow", "schedules": [{ "cron": "0 0 * * *" }] } ], "r2_buckets": [ { "binding": "BACKUP_BUCKET", "bucket_name": "d1-backups" } ] }