backend-development
Backend development patterns for the Flare Stack Blog. Use when creating new feature modules, writing Server Functions, defining Zod schemas, implementing Cloudflare Workflows, or working with middlewares.
安装与使用
请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。
查看来源 ↗使用指南技能说明
Backend Development Patterns
This skill covers the backend module architecture, Server Functions, schema definitions, and async workflows.
Module Layering
Each feature module (features/<name>) follows a strict three-layer architecture:
1. Data Layer (data/[name].data.ts)
- Responsibility: Pure data access. All Drizzle ORM queries live here.
- Dependencies: Only receives the DB instance.
- Prohibited: No business logic, no caching, no API interactions.
// posts.data.ts
export const PostRepo = {
findPostById: (db: DB, id: number) =>
db.select().from(posts).where(eq(posts.id, id)).get(),
};
2. Service Layer ([name].service.ts)
- Responsibility: Business logic orchestration. Data transformation, caching, calling other services.
-
Dependencies: Receives typed Context object. Use the most specific type needed:
-
DbContextfor database-only operations -
DbContext & { executionCtx: ExecutionContext }for operations with background tasks -
AuthContextfor authenticated operations
-
-
Caching: This is the ONLY layer that calls
CacheService.get()andCacheService.bumpVersion().
// posts.service.ts
import { POSTS_CACHE_KEYS } from "./posts.schema";
export async function findPostBySlug(
context: DbContext & { executionCtx: ExecutionContext },
data: { slug: string }
) {
const fetcher = () => PostRepo.findPostBySlug(context.db, data.slug);
const version = await CacheService.getVersion(context, "posts:detail");
return CacheService.get(context, POSTS_CACHE_KEYS.detail(version, data.slug), PostSchema, fetcher);
}
export async function createEmptyPost(context: DbContext) {
// No executionCtx needed - simple DB operation
return await PostRepo.insertPost(context.db, { ... });
}
3. API Layer (api/[name].api.ts)
- Responsibility: Define Server Functions as frontend RPC entry points. Handle auth and input validation.
-
Pattern: Use
createServerFn()with middleware chains to progressively build context.
Middleware Composition
import { createServerFn } from "@tanstack/react-start";
import {
adminMiddleware,
createCacheHeaderMiddleware,
createRateLimitMiddleware,
} from "@/lib/middlewares";
// Public endpoint with caching
export const getPostsFn = createServerFn()
.middleware([
createRateLimitMiddleware({
capacity: 60,
interval: "1m",
key: "posts:list",
}),
createCacheHeaderMiddleware("swr"), // Sets Cache-Control headers
])
.inputValidator(GetPostsInputSchema)
.handler(({ data, context }) => PostService.getPosts(context, data));
// Admin endpoint (private cache + auth required)
export const updatePostFn = createServerFn()
.middleware([adminMiddleware]) // Includes dbMiddleware + sessionMiddleware + auth check + private cache
.inputValidator(UpdatePostInputSchema)
.handler(({ data, context }) => PostService.updatePost(context, data));
Cache Header Strategies
| Strategy | Header | Use Case |
|---|---|---|
"private" |
no-store, private | Auth/admin responses |
"immutable" |
Long-term immutable | Hashed static assets |
"swr" |
Stale-while-revalidate | General caching |
Schema Definitions ([name].schema.ts)
Use drizzle-zod to auto-generate base schemas from Drizzle table definitions:
import { createSelectSchema, createInsertSchema } from "drizzle-zod";
import { posts } from "@/lib/db/schema";
// Base schemas
export const PostSelectSchema = createSelectSchema(posts);
export const PostInsertSchema = createInsertSchema(posts);
// Derived API schemas
export const CreatePostInputSchema = PostInsertSchema.pick({
title: true,
content: true,
});
export const PostResponseSchema = PostSelectSchema.extend({
author: UserSchema,
});
// Type exports
export type Post = z.infer<typeof PostSelectSchema>;
export type CreatePostInput = z.infer<typeof CreatePostInputSchema>;
Cache Key Factories
Define factories for KV cache keys to ensure consistency and type safety across the service and API layers.
export const POSTS_CACHE_KEYS = {
detail: (version: string, slug: string) => [version, "post", slug] as const,
list: (version: string, limit: number, cursor: number) =>
["posts", "list", version, limit, cursor] as const,
} as const;
TanStack Middlewares (lib/middlewares.ts)
Middlewares progressively inject dependencies and enforce policies.
Infrastructure Middlewares (DI)
| Middleware | Injects | Depends On |
|---|---|---|
dbMiddleware |
db |
(base context) |
sessionMiddleware |
auth, session |
dbMiddleware |
Policy Middlewares
| Middleware | Purpose | Depends On |
|---|---|---|
authMiddleware |
Requires valid session (401 if missing) | sessionMiddleware |
adminMiddleware |
Requires admin role (403 if not admin) | authMiddleware |
createCacheHeaderMiddleware(strategy) |
Sets Cache-Control headers | (none) |
createRateLimitMiddleware(options) |
Calls Durable Object for rate limiting | sessionMiddleware |
Middleware Chain Example
// adminMiddleware already includes the full chain:
// dbMiddleware -> sessionMiddleware -> private cache -> auth check -> admin check
export const adminMiddleware = createMiddleware()
.middleware([authMiddleware]) // authMiddleware includes sessionMiddleware which includes dbMiddleware
.server(async ({ next, context }) => {
if (context.session.user.role !== "admin") {
throw json({ message: "PERMISSION_DENIED" }, { status: 403 });
}
return next({ context: { session } });
});
Cloudflare Workflows
For complex async task orchestration with persistent state.
Structure
export class PostProcessWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
await step.do("process-content", async () => {
// Atomic, retryable step
});
}
}
Retry Configuration
await step.do("step-name", {
retries: { limit: 3, delay: "5 seconds", backoff: "exponential" },
}, async () => { ... });
Triggering
await context.env.POST_PROCESS_WORKFLOW.create({
params: { postId, isPublished },
});
Registration
- Export workflow class in
src/server.ts - Declare in
wrangler.jsoncworkflows array
Environment Variables
Server-only env vars with Zod validation in src/lib/env/server.env.ts:
import { serverEnv } from "@/lib/env/server.env";
const env = serverEnv(context.env);
const domain = env.DOMAIN;
Config Files:
-
.dev.vars: Local development (not committed) - Cloudflare Dashboard / Wrangler Secrets: Production
Code Quality Checks
After any code changes, always run:
# Type checking
bun tsc --noEmit
# Lint & Format
bun check
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Files (layers) | [name].[layer].ts |
posts.service.ts |
| Server Functions | camelCase + Fn suffix |
getPostsFn |
| Constants | SCREAMING_SNAKE_CASE | CACHE_CONTROL |
使用示例
查看来源仓库中的使用示例与配置说明。
查看来源 ↗来源历史
- 收录时间
- 2026-01-20
- 索引更新时间
- 2026-01-26
- Branch
- main
收录与同步日期属于本站记录。源码修改历史请查看来源仓库。
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!