From cf-saas-stack
Context-based client pattern for passing external service clients through request context
How this skill is triggered — by the user, by Claude, or both
Slash command
/cf-saas-stack:context-clientsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
External service clients (Stripe, AI, analytics, etc.) are created once at the request level and passed through context. Repositories and services receive clients as parameters rather than creating them.
External service clients (Stripe, AI, analytics, etc.) are created once at the request level and passed through context. Repositories and services receive clients as parameters rather than creating them.
Create clients in the Cloudflare Worker and pass to React Router context:
// workers/app.ts
export default {
async fetch(request, env, ctx) {
const auth = await createAuth(env.DATABASE, { secret: env.AUTH_SECRET });
const stripe = env.STRIPE_SECRET_KEY ? createStripeClient(env.STRIPE_SECRET_KEY) : null;
const ai = createAIClient(env.AI_API_KEY);
return requestHandler(request, { cloudflare: { env, ctx }, auth, stripe, ai });
},
} satisfies ExportedHandler<Env>;
Run bun typegen to generate the AppLoadContext types automatically.
Pass clients from Cloudflare context to tRPC context:
// app/trpc/index.ts
export const createTRPCContext = async (opts) => {
const db = await getDb(opts.cfContext.DATABASE);
const stripe = opts.cfContext.STRIPE_SECRET_KEY ? createStripeClient(opts.cfContext.STRIPE_SECRET_KEY) : null;
const ai = createAIClient(opts.cfContext.AI_API_KEY);
return { db, stripe, ai, cfContext: opts.cfContext };
};
Access clients from context, pass to repositories:
export const paymentRouter = createTRPCRouter({
createPayment: protectedProcedure
.input(z.object({ amount: z.number() }))
.mutation(async ({ ctx, input }) => {
if (!ctx.stripe) {
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Payment system not configured" });
}
return await paymentRepository.createPayment(ctx.db, ctx.stripe, input);
}),
});
Repositories receive clients as parameters:
// CORRECT: Receive client as parameter
export async function createPayment(db: Database, stripe: StripeClient, input: CreatePaymentInput) {
const paymentIntent = await stripe.paymentIntents.create({ amount: input.amount, currency: "usd" });
}
// WRONG: Creating client inside repository
export async function createPaymentBad(db: Database, stripeSecretKey: string, input: CreatePaymentInput) {
const stripe = new Stripe(stripeSecretKey); // Don't create here
}
Create simple factory functions in app/lib/:
// app/lib/stripe.ts
export function createStripeClient(secretKey: string): Stripe {
return new Stripe(secretKey, { apiVersion: "2024-06-20" });
}
Always check for optional clients before use:
if (!ctx.stripe) {
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Payment system is not configured" });
}
await ctx.stripe.customers.create({ ... });
| Layer | Responsibility |
|---|---|
workers/app.ts | Create clients from env vars |
app/trpc/index.ts | Include clients in tRPC context |
app/trpc/routes/*.ts | Access clients from ctx, pass to repos |
app/repositories/*.ts | Receive clients as parameters |
app/lib/*.ts | Factory functions for client creation |
npx claudepluginhub casper-studios/casper-marketplace --plugin cf-saas-stackProvides environment variable access patterns for Cloudflare Workers, including client creation in tRPC context and React Router loaders. Useful when adding new external services or debugging env binding issues.
Injects database clients, sessions, and request data into every tRPC procedure via createTRPCContext. Covers context type definition, middleware extension, and adapter wiring.
Scaffolds a new service integration with init/accessor pattern, registration in setup(), and typed service file. Use when adding an external API or creating a reusable domain module.