Cloudflare Workers + Drizzle
Integrate fragments with Cloudflare Workers, React Router v7, and Drizzle
This guide shows how to integrate Fragno Fragments into a Cloudflare Workers application using React Router v7, Neon PostgreSQL, and Drizzle ORM, using the Stripe fragment as an example.
Prerequisites
- A React Router v7 project configured for Cloudflare Workers
- Any PostgreSQL database (such as Neon or PlanetScale)
- Basic familiarity with Drizzle ORM
Step 1: Installation
Install the Stripe fragment and required dependencies:
npm install @fragno-dev/stripe @fragno-dev/db drizzle-orm pg dotenv
npm install -D @fragno-dev/cli drizzle-kit @types/pgStep 2: Database Configuration
2.1 Configure Environment Variables
Create a .dev.vars file in your project root:
PG_DATABASE_URL=postgresql://user:password@your-neon-host.neon.tech/dbname?sslmode=require
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...For production, set these in your Cloudflare Workers secrets:
wrangler secret put PG_DATABASE_URL
wrangler secret put STRIPE_SECRET_KEY
wrangler secret put STRIPE_WEBHOOK_SECRET2.2 Create Database Client and Drizzle Setup
Create app/db/postgres.ts:
import { drizzle } from "drizzle-orm/node-postgres";
import { schema } from "./postgres.schema.ts";
import { Client } from "pg";
export function createPostgresClient() {
return new Client({
connectionString: process.env.PG_DATABASE_URL!,
});
}
export function createDrizzleDatabase(client: Client) {
return drizzle({ client, schema });
}
export type DrizzleDatabase = ReturnType<typeof createDrizzleDatabase>;Cloudflare Worker invocations are short-lived and stateless, we therefore want to establish a new
database connection for each request. The same pattern would also work if we later decided to start
using Cloudflare Hyperdrive, in that case we'd obtain the HYPERDRIVE instance from the env
context in the Fetch handler.
2.3 Create Drizzle Adapter for Fragno
Create app/fragno/database-adapter.ts:
import { DrizzleAdapter } from "@fragno-dev/db/adapters/drizzle";
import type { DrizzleDatabase } from "../db/postgres.ts";
export function createAdapter(db: DrizzleDatabase | (() => DrizzleDatabase)) {
if (typeof db === "function") {
return new DrizzleAdapter({
db: db(),
provider: "postgresql",
});
}
return new DrizzleAdapter({
db,
provider: "postgresql",
});
}This adapter accepts either a database instance or a factory function, providing flexibility for different initialization patterns.
Step 3: Create Server-Side Fragment Instance
Create app/fragno/stripe-server.ts:
import { createStripeFragment } from "@fragno-dev/stripe";
import { createAdapter } from "./database-adapter.ts";
import {
createPostgresClient,
createDrizzleDatabase,
type DrizzleDatabase,
} from "../db/postgres.ts";
export function createStripeServer(db: DrizzleDatabase | (() => DrizzleDatabase)) {
return createStripeFragment(
{
stripeSecretKey: process.env.STRIPE_SECRET_KEY!,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
onStripeCustomerCreated: async (stripeCustomerId, referenceId) => {
// Update your user/org record with the Stripe customer ID
},
resolveEntityFromRequest: async (context) => {
// Return authenticated user/org info
return {
referenceId: "user_id",
customerEmail: "user@example.com",
};
},
},
{
databaseAdapter: createAdapter(db),
},
);
}
// Top-level instance for CLI tools (schema generation)
export const fragment = createStripeServer(() => {
const client = createPostgresClient();
return createDrizzleDatabase(client);
});It's important for the Fragment instance to be exported on the top-level so that the Fragno CLI can find it and generate the schema file.
Step 4: Configure Cloudflare Worker
Update your workers/app.ts to create database connections per request and pass them via context:
import { createRequestHandler } from "react-router";
import { createDrizzleDatabase, createPostgresClient, type DrizzleDatabase } from "~/db/postgres";
declare module "react-router" {
export interface AppLoadContext {
cloudflare: {
env: CloudflareEnv;
ctx: ExecutionContext;
};
db: DrizzleDatabase;
}
}
const requestHandler = createRequestHandler(
() => import("virtual:react-router/server-build"),
import.meta.env.MODE,
);
export default {
async fetch(request, env, ctx) {
const client = createPostgresClient();
try {
await client.connect();
const db = createDrizzleDatabase(client);
return await requestHandler(request, {
cloudflare: { env, ctx },
db,
});
} catch (error) {
console.error("Error fetching request", error);
return new Response("Internal Server Error", { status: 500 });
} finally {
await client.end();
}
},
} satisfies ExportedHandler<CloudflareEnv>;Key Points:
- Database connection is created per request
- Connection is properly cleaned up in the
finallyblock - Database instance is passed via
AppLoadContextfor type-safe access in routes
Step 5: Mount API Routes
Create a React Router v7 API route at app/routes/api/stripe.tsx:
import { createStripeServer } from "~/fragno/stripe-server";
import type { Route } from "./+types/stripe";
export async function loader({ request, context }: Route.LoaderArgs) {
return createStripeServer(context.db).handler(request);
}
export async function action({ request, context }: Route.ActionArgs) {
return createStripeServer(context.db).handler(request);
}This creates a catch-all route at /api/stripe that forwards all requests to the Fragment handler.
Both loader (GET) and action (POST/PUT/DELETE) are required to handle all HTTP methods.
Step 6: Schema Management
Fragno Fragments define their own schema that can be merged with your application schema. This means that you can query tables from the Fragment in your application code, using Drizzle's type-safe query builder.
6.1 Configure Drizzle Kit for Dual Schemas
Update your Drizzle config to also include the Fragno-generated schema.
import { defineConfig } from "drizzle-kit";
import dotenv from "dotenv";
dotenv.config({
path: ".dev.vars",
});
export default defineConfig({
dialect: "postgresql",
schema: ["./app/db/postgres/postgres.schema.ts", "./app/db/postgres/fragno-schema.ts"],
out: "./app/db/postgres/migrations",
dbCredentials: {
url: process.env.PG_DATABASE_URL!,
},
});Important: Both your application schema and the Fragno-generated schema must be included in the
schema array.
6.2 Setup Package Scripts
Update your package.json to include the following script to generate the Fragno schema:
{
"scripts": {
"db:fragno:generate": "npx @fragno-dev/cli db generate app/fragno/stripe-server.ts -o app/db/postgres/fragno-schema.ts",
// ...
},
}6.3 Generate and Merge Schemas
-
Generate Fragno schema (creates Fragment database tables):
npm run db:fragno:generateThis creates
app/db/postgres/fragno-schema.tswith the Fragment's database schema. -
Create your application schema at
app/db/postgres/postgres.schema.ts:import { pgTable, text, serial, timestamp } from "drizzle-orm/pg-core"; import { stripe_db_schema } from "./fragno-schema.ts"; export const users = pgTable("users", { id: serial().primaryKey(), name: text().notNull(), email: text(), stripeCustomerId: text(), }); // Export combined schema export const schema = { users, // ... your other tables ...stripe_db_schema, // Include Fragment tables }; -
Generate and run migrations:
Depending on your method of choice you can either use generate or push to apply the migrations.
npm run db:postgres:generate
npm run db:postgres:push6.4 Schema Update Workflow
When the Fragment is updated and requires schema changes, you can run the Fragno generate command again to update the Fragno-Drizzle schema.
npm run db:fragno:generateStep 7: Client-Side Integration
7.1 Create Client Instance
Create app/lib/stripe-client.ts:
import { createStripeClient } from "@fragno-dev/stripe/react";
export const stripeClient = createStripeClient();Note: The file should NOT end with .client.ts as this prevents server-side rendering in some
frameworks.
7.2 Use in React Components
import { stripeClient } from "~/lib/stripe-client";
export default function SubscribeButton() {
const { mutate, loading, error } = stripeClient.upgradeSubscription();
const handleSubscribe = async () => {
const { url, redirect } = await mutate({
body: {
priceId: "price_1234567890",
successUrl: `${window.location.origin}/success`,
cancelUrl: window.location.href,
},
});
if (redirect) {
window.location.href = url;
}
};
return (
<button onClick={handleSubscribe} disabled={loading}>
{loading ? "Loading..." : "Subscribe"}
</button>
);
}That's it!
The subscriptions in your database are now kept in sync with Stripe through webhook events. You can
query the subscriptions directly using Drizzle or use the helpers either client-side via the
stripeClient or server-side via the stripeFragment instance.
Next Steps
For more information about the Stripe fragment: