Fullstack Dev
infometa/workbuddyskills
Full-stack backend architecture and frontend-backend integration guide.
Full-stack backend architecture and frontend-backend integration guide.
$ npx skills add HHU3637kr/skills --skill fullstack-dev -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install HHU3637kr/skills fullstack-dev --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/HHU3637kr/skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/fullstack-dev .claude/skills/fullstack-dev && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "fullstack-dev" agent skill from https://github.com/HHU3637kr/skills/tree/master/fullstack-dev into .claude/skills/fullstack-dev/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fullstack-dev", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/HHU3637kr/skills/tree/master/fullstack-devType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add HHU3637kr/skills --skill fullstack-dev -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install HHU3637kr/skills fullstack-dev --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/HHU3637kr/skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/fullstack-dev .agents/skills/fullstack-dev && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "fullstack-dev" agent skill from https://github.com/HHU3637kr/skills/tree/master/fullstack-dev into .agents/skills/fullstack-dev/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fullstack-dev", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add HHU3637kr/skills --skill fullstack-dev -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install HHU3637kr/skills fullstack-dev --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/HHU3637kr/skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/fullstack-dev .cursor/skills/fullstack-dev && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "fullstack-dev" agent skill from https://github.com/HHU3637kr/skills/tree/master/fullstack-dev into .cursor/skills/fullstack-dev/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fullstack-dev", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/HHU3637kr/skills.git --path fullstack-dev--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add HHU3637kr/skills --skill fullstack-dev -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install HHU3637kr/skills fullstack-dev --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/HHU3637kr/skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/fullstack-dev .gemini/skills/fullstack-dev && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "fullstack-dev" agent skill from https://github.com/HHU3637kr/skills/tree/master/fullstack-dev into .gemini/skills/fullstack-dev/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fullstack-dev", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install HHU3637kr/skills fullstack-devInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add HHU3637kr/skills --skill fullstack-dev -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/HHU3637kr/skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/fullstack-dev .github/skills/fullstack-dev && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "fullstack-dev" agent skill from https://github.com/HHU3637kr/skills/tree/master/fullstack-dev into .github/skills/fullstack-dev/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fullstack-dev", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add HHU3637kr/skills --skill fullstack-dev -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install HHU3637kr/skills fullstack-dev --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/HHU3637kr/skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/fullstack-dev .opencode/skills/fullstack-dev && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "fullstack-dev" agent skill from https://github.com/HHU3637kr/skills/tree/master/fullstack-dev into .opencode/skills/fullstack-dev/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fullstack-dev", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
fullstack-devFull-stack backend architecture and frontend-backend integration guide.
Fullstack Dev is an agent skill from HHU3637kr/skills. Full-stack backend architecture and frontend-backend integration guide. TRIGGER when: building a full-stack app, creating REST API with frontend, scaffolding backend service, building todo app, building CRUD app, building real-time app, building chat app, Express + React, Next.js API, Node.js backend, Python backend, Go backend, designing service layers, implementing error handling, managing config/auth, setting up API clients, implementing auth flows, handling file uploads, adding real-time features…
Its SKILL.md is about 8.6k tokens, which your agent loads only when the skill is triggered. The skill folder holds 9 other files, including reference files (for example `references/api-design.md`, `references/auth-flow.md` and `references/db-schema.md`).
It sits in Backend & APIs, covering Realtime and WebSockets, Authentication and Project scaffolding. It works with React, Next.js, Python and Node.js. The licence is MIT.
6 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit a2cccf6. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npxnpmcurlFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npx, npm and curl, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
JWT_SECRETFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Fullstack Dev loads about 8.6k tokens when it runs, and up to ~30k if it reads all its reference files. Until then it costs about 162 tokens; SKILL.md has 1,519 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
env_file = ".env"❌ Never commit .env filesAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from HHU3637kr/skills at commit a2cccf6, republished under its MIT licence (© HHU3637kr). 1,519 words, ~8,626 tokens.
.claude/skills/fullstack-dev/SKILL.md (or your agent's skills folder). This skill also uses 8 other files; get the full folder from GitHub.When this skill is triggered, you MUST follow this workflow before writing any code.
Before scaffolding anything, ask the user to clarify (or infer from context):
If the user has already specified these in their request, skip asking and proceed.
Based on requirements, make and state these decisions before coding:
| Decision | Options | Reference |
|---|---|---|
| Project structure | Feature-first (recommended) vs layer-first | Section 1 |
| API client approach | Typed fetch / React Query / tRPC / OpenAPI codegen | Section 5 |
| Auth strategy | JWT + refresh / session / third-party | Section 6 |
| Real-time method | Polling / SSE / WebSocket | Section 11 |
| Error handling | Typed error hierarchy + global handler | Section 3 |
Briefly explain each choice (1 sentence per decision).
Use the appropriate checklist below. Ensure ALL checked items are implemented — do not skip any.
Write code following the patterns in this document. Reference specific sections as you implement each part.
After implementation, run these checks before claiming completion:
# Backend
cd server && npm run build
# Frontend
cd client && npm run build# Start server, then test
curl http://localhost:3000/health
curl http://localhost:3000/api/<resource>If any check fails, fix the issue before proceeding.
Provide a brief summary to the user:
USE this skill when:
NOT for:
Error)/health, /ready)*).env.example committed (no real secrets)* in production)| Need to… | Jump to |
|---|---|
| Organize project folders | 1. Project Structure |
| Manage config + secrets | 2. Configuration |
| Handle errors properly | 3. Error Handling |
| Write database code | 4. Database Access Patterns |
| Set up API client from frontend | 5. API Client Patterns |
| Add auth middleware | 6. Auth & Middleware |
| Set up logging | 7. Logging & Observability |
| Add background jobs | 8. Background Jobs |
| Implement caching | 9. Caching |
| Upload files (presigned URL, multipart) | 10. File Upload Patterns |
| Add real-time features (SSE, WebSocket) | 11. Real-Time Patterns |
| Handle API errors in frontend UI | 12. Cross-Boundary Error Handling |
| Harden for production | 13. Production Hardening |
| Design API endpoints | API Design |
| Design database schema | Database Schema |
| Auth flow (JWT, refresh, Next.js SSR, RBAC) | references/auth-flow.md |
| CORS, env vars, environment management | references/environment-management.md |
1. ✅ Organize by FEATURE, not by technical layer
2. ✅ Controllers never contain business logic
3. ✅ Services never import HTTP request/response types
4. ✅ All config from env vars, validated at startup, fail fast
5. ✅ Every error is typed, logged, and returns consistent format
6. ✅ All input validated at the boundary — trust nothing from client
7. ✅ Structured JSON logging with request ID — not console.log✅ Feature-first ❌ Layer-first
src/ src/
orders/ controllers/
order.controller.ts order.controller.ts
order.service.ts user.controller.ts
order.repository.ts services/
order.dto.ts order.service.ts
order.test.ts user.service.ts
users/ repositories/
user.controller.ts ...
user.service.ts
shared/
database/
middleware/Controller (HTTP) → Service (Business Logic) → Repository (Data Access)| Layer | Responsibility | ❌ Never |
|---|---|---|
| Controller | Parse request, validate, call service, format response | Business logic, DB queries |
| Service | Business rules, orchestration, transaction mgmt | HTTP types (req/res), direct DB |
| Repository | Database queries, external API calls | Business logic, HTTP types |
TypeScript:
class OrderService {
constructor(
private readonly orderRepo: OrderRepository, // ✅ injected interface
private readonly emailService: EmailService,
) {}
}Python:
class OrderService:
def __init__(self, order_repo: OrderRepository, email_service: EmailService):
self.order_repo = order_repo # ✅ injected
self.email_service = email_serviceGo:
type OrderService struct {
orderRepo OrderRepository // ✅ interface
emailService EmailService
}
func NewOrderService(repo OrderRepository, email EmailService) *OrderService {
return &OrderService{orderRepo: repo, emailService: email}
}TypeScript:
const config = {
port: parseInt(process.env.PORT || '3000', 10),
database: { url: requiredEnv('DATABASE_URL'), poolSize: intEnv('DB_POOL_SIZE', 10) },
auth: { jwtSecret: requiredEnv('JWT_SECRET'), expiresIn: process.env.JWT_EXPIRES_IN || '1h' },
} as const;
function requiredEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing required env var: ${name}`); // fail fast
return value;
}Python:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str # required — app won't start without it
jwt_secret: str # required
port: int = 3000 # optional with default
db_pool_size: int = 10
class Config:
env_file = ".env"
settings = Settings() # fails fast if DATABASE_URL missing✅ All config via environment variables (Twelve-Factor)
✅ Validate required vars at startup — fail fast
✅ Type-cast at config layer, not at usage sites
✅ Commit .env.example with dummy values
❌ Never hardcode secrets, URLs, or credentials
❌ Never commit .env files
❌ Never scatter process.env / os.environ throughout code// Base (TypeScript)
class AppError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode: number,
public readonly isOperational: boolean = true,
) { super(message); }
}
class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(`${resource} not found: ${id}`, 'NOT_FOUND', 404);
}
}
class ValidationError extends AppError {
constructor(public readonly errors: FieldError[]) {
super('Validation failed', 'VALIDATION_ERROR', 422);
}
}# Base (Python)
class AppError(Exception):
def __init__(self, message: str, code: str, status_code: int):
self.message, self.code, self.status_code = message, code, status_code
class NotFoundError(AppError):
def __init__(self, resource: str, id: str):
super().__init__(f"{resource} not found: {id}", "NOT_FOUND", 404)// TypeScript (Express)
app.use((err, req, res, next) => {
if (err instanceof AppError && err.isOperational) {
return res.status(err.statusCode).json({
title: err.code, status: err.statusCode,
detail: err.message, request_id: req.id,
});
}
logger.error('Unexpected error', { error: err.message, stack: err.stack, request_id: req.id });
res.status(500).json({ title: 'Internal Error', status: 500, request_id: req.id });
});✅ Typed, domain-specific error classes
✅ Global error handler catches everything
✅ Operational errors → structured response
✅ Programming errors → log + generic 500
✅ Retry transient failures with exponential backoff
❌ Never catch and ignore errors silently
❌ Never return stack traces to client
❌ Never throw generic Error('something')# TypeScript (Prisma) # Python (Alembic) # Go (golang-migrate)
npx prisma migrate dev alembic revision --autogenerate migrate -source file://migrations
npx prisma migrate deploy alembic upgrade head migrate -database $DB up✅ Schema changes via migrations, never manual SQL
✅ Migrations must be reversible
✅ Review migration SQL before production
❌ Never modify production schema manually// ❌ N+1: 1 query + N queries
const orders = await db.order.findMany();
for (const o of orders) { o.items = await db.item.findMany({ where: { orderId: o.id } }); }
// ✅ Single JOIN query
const orders = await db.order.findMany({ include: { items: true } });await db.$transaction(async (tx) => {
const order = await tx.order.create({ data: orderData });
await tx.inventory.decrement({ productId, quantity });
await tx.payment.create({ orderId: order.id, amount });
});Pool size = (CPU cores × 2) + spindle_count (start with 10-20). Always set connection timeout. Use PgBouncer for serverless.
The "glue layer" between frontend and backend. Choose the approach that fits your team and stack.
// lib/api-client.ts
const BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
class ApiError extends Error {
constructor(public status: number, public body: any) {
super(body?.detail || body?.message || `API error ${status}`);
}
}
async function api<T>(path: string, options: RequestInit = {}): Promise<T> {
const token = getAuthToken(); // from cookie / memory / context
const res = await fetch(`${BASE_URL}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
});
if (!res.ok) {
const body = await res.json().catch(() => null);
throw new ApiError(res.status, body);
}
if (res.status === 204) return undefined as T;
return res.json();
}
export const apiClient = {
get: <T>(path: string) => api<T>(path),
post: <T>(path: string, data: unknown) => api<T>(path, { method: 'POST', body: JSON.stringify(data) }),
put: <T>(path: string, data: unknown) => api<T>(path, { method: 'PUT', body: JSON.stringify(data) }),
patch: <T>(path: string, data: unknown) => api<T>(path, { method: 'PATCH', body: JSON.stringify(data) }),
delete: <T>(path: string) => api<T>(path, { method: 'DELETE' }),
};// hooks/use-orders.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '@/lib/api-client';
interface Order { id: string; total: number; status: string; }
interface CreateOrderInput { items: { productId: string; quantity: number }[] }
export function useOrders() {
return useQuery({
queryKey: ['orders'],
queryFn: () => apiClient.get<{ data: Order[] }>('/api/orders'),
staleTime: 1000 * 60, // 1 min
});
}
export function useCreateOrder() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateOrderInput) =>
apiClient.post<{ data: Order }>('/api/orders', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['orders'] });
},
});
}
// Usage in component:
function OrdersPage() {
const { data, isLoading, error } = useOrders();
const createOrder = useCreateOrder();
if (isLoading) return <Skeleton />;
if (error) return <ErrorBanner error={error} />;
// ...
}// server: trpc/router.ts
export const appRouter = router({
orders: router({
list: publicProcedure.query(async () => {
return db.order.findMany({ include: { items: true } });
}),
create: protectedProcedure
.input(z.object({ items: z.array(orderItemSchema) }))
.mutation(async ({ input, ctx }) => {
return orderService.create(ctx.user.id, input);
}),
}),
});
export type AppRouter = typeof appRouter;
// client: automatic type safety, no code generation
const { data } = trpc.orders.list.useQuery();
const createOrder = trpc.orders.create.useMutation();npx openapi-typescript-codegen \
--input http://localhost:3001/api/openapi.json \
--output src/generated/api \
--client axios| Approach | When | Type Safety | Effort |
|---|---|---|---|
| Typed fetch wrapper | Simple apps, small teams | Manual types | Low |
| React Query + fetch | React apps, server state | Manual types | Medium |
| tRPC | Same team, TypeScript both sides | Automatic | Low |
| OpenAPI generated | Public API, multi-consumer | Automatic | Medium |
| GraphQL codegen | GraphQL APIs | Automatic | Medium |
Full reference: references/auth-flow.md — JWT bearer flow, automatic token refresh, Next.js server-side auth, RBAC pattern, backend middleware order.
Request → 1.RequestID → 2.Logging → 3.CORS → 4.RateLimit → 5.BodyParse
→ 6.Auth → 7.Authz → 8.Validation → 9.Handler → 10.ErrorHandler → Response✅ Short expiry access token (15min) + refresh token (server-stored)
✅ Minimal claims: userId, roles (not entire user object)
✅ Rotate signing keys periodically
❌ Never store tokens in localStorage (XSS risk)
❌ Never pass tokens in URL query paramsfunction authorize(...roles: Role[]) {
return (req, res, next) => {
if (!req.user) throw new UnauthorizedError();
if (!roles.some(r => req.user.roles.includes(r))) throw new ForbiddenError();
next();
};
}
router.delete('/users/:id', authenticate, authorize('admin'), deleteUser);// lib/api-client.ts — transparent refresh on 401
async function apiWithRefresh<T>(path: string, options: RequestInit = {}): Promise<T> {
try {
return await api<T>(path, options);
} catch (err) {
if (err instanceof ApiError && err.status === 401) {
const refreshed = await api<{ accessToken: string }>('/api/auth/refresh', {
method: 'POST',
credentials: 'include', // send httpOnly cookie
});
setAuthToken(refreshed.accessToken);
return api<T>(path, options); // retry
}
throw err;
}
}// ✅ Structured — parseable, filterable, alertable
logger.info('Order created', {
orderId: order.id, userId: user.id, total: order.total,
items: order.items.length, duration_ms: Date.now() - startTime,
});
// Output: {"level":"info","msg":"Order created","orderId":"ord_123",...}
// ❌ Unstructured — useless at scale
console.log(`Order created for user ${user.id} with total ${order.total}`);| Level | When | Production? |
|---|---|---|
| error | Requires immediate attention | ✅ Always |
| warn | Unexpected but handled | ✅ Always |
| info | Normal operations, audit trail | ✅ Always |
| debug | Dev troubleshooting | ❌ Dev only |
✅ Request ID in every log entry (propagated via middleware)
✅ Log at layer boundaries (request in, response out, external call)
❌ Never log passwords, tokens, PII, or secrets
❌ Never use console.log in production code✅ All jobs must be IDEMPOTENT (same job running twice = same result)
✅ Failed jobs → retry (max 3) → dead letter queue → alert
✅ Workers run as SEPARATE processes (not threads in API server)
❌ Never put long-running tasks in request handlers
❌ Never assume job runs exactly onceasync function processPayment(data: { orderId: string }) {
const order = await orderRepo.findById(data.orderId);
if (order.paymentStatus === 'completed') return; // already processed
await paymentGateway.charge(order);
await orderRepo.updatePaymentStatus(order.id, 'completed');
}async function getUser(id: string): Promise<User> {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await userRepo.findById(id);
if (!user) throw new NotFoundError('User', id);
await redis.set(`user:${id}`, JSON.stringify(user), 'EX', 900); // 15min TTL
return user;
}✅ ALWAYS set TTL — never cache without expiry
✅ Invalidate on write (delete cache key after update)
✅ Use cache for reads, never for authoritative state
❌ Never cache without TTL (stale data is worse than slow data)| Data Type | Suggested TTL |
|---|---|
| User profile | 5-15 min |
| Product catalog | 1-5 min |
| Config / feature flags | 30-60 sec |
| Session | Match session duration |
Client → GET /api/uploads/presign?filename=photo.jpg&type=image/jpeg
Server → { uploadUrl: "https://s3.../presigned", fileKey: "uploads/abc123.jpg" }
Client → PUT uploadUrl (direct to S3, bypasses your server)
Client → POST /api/photos { fileKey: "uploads/abc123.jpg" } (save reference)Backend:
app.get('/api/uploads/presign', authenticate, async (req, res) => {
const { filename, type } = req.query;
const key = `uploads/${crypto.randomUUID()}-${filename}`;
const url = await s3.getSignedUrl('putObject', {
Bucket: process.env.S3_BUCKET, Key: key,
ContentType: type, Expires: 300, // 5 min
});
res.json({ uploadUrl: url, fileKey: key });
});Frontend:
async function uploadFile(file: File) {
const { uploadUrl, fileKey } = await apiClient.get<PresignResponse>(
`/api/uploads/presign?filename=${file.name}&type=${file.type}`
);
await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } });
return apiClient.post('/api/photos', { fileKey });
}// Frontend
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'Profile photo');
const res = await fetch('/api/upload', { method: 'POST', body: formData });
// Note: do NOT set Content-Type header — browser sets boundary automatically| Method | File Size | Server Load | Complexity |
|---|---|---|---|
| Presigned URL | Any (recommended > 5MB) | None (direct to storage) | Medium |
| Multipart | < 10MB | High (streams through server) | Low |
| Chunked / Resumable | > 100MB | Medium | High |
Best for: notifications, live feeds, streaming AI responses.
Backend (Express):
app.get('/api/events', authenticate, (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
});
const send = (event: string, data: unknown) => {
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
};
const unsubscribe = eventBus.subscribe(req.user.id, (event) => {
send(event.type, event.payload);
});
req.on('close', () => unsubscribe());
});Frontend:
function useServerEvents(userId: string) {
useEffect(() => {
const source = new EventSource(`/api/events?userId=${userId}`);
source.addEventListener('notification', (e) => {
showToast(JSON.parse(e.data).message);
});
source.onerror = () => { source.close(); setTimeout(() => /* reconnect */, 3000); };
return () => source.close();
}, [userId]);
}Best for: chat, collaborative editing, gaming.
Backend (ws library):
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ server: httpServer, path: '/ws' });
wss.on('connection', (ws, req) => {
const userId = authenticateWs(req);
if (!userId) { ws.close(4001, 'Unauthorized'); return; }
ws.on('message', (raw) => handleMessage(userId, JSON.parse(raw.toString())));
ws.on('close', () => cleanupUser(userId));
const interval = setInterval(() => ws.ping(), 30000);
ws.on('pong', () => { /* alive */ });
ws.on('close', () => clearInterval(interval));
});Frontend:
function useWebSocket(url: string) {
const [ws, setWs] = useState<WebSocket | null>(null);
useEffect(() => {
const socket = new WebSocket(url);
socket.onopen = () => setWs(socket);
socket.onclose = () => setTimeout(() => /* reconnect */, 3000);
return () => socket.close();
}, [url]);
const send = useCallback((data: unknown) => ws?.send(JSON.stringify(data)), [ws]);
return { ws, send };
}function useOrderStatus(orderId: string) {
return useQuery({
queryKey: ['order-status', orderId],
queryFn: () => apiClient.get<Order>(`/api/orders/${orderId}`),
refetchInterval: (query) => {
if (query.state.data?.status === 'completed') return false;
return 5000;
},
});
}| Method | Direction | Complexity | When |
|---|---|---|---|
| Polling | Client → Server | Low | Simple status checks, < 10 clients |
| SSE | Server → Client | Medium | Notifications, feeds, AI streaming |
| WebSocket | Bidirectional | High | Chat, collaboration, gaming |
// lib/error-handler.ts
export function getErrorMessage(error: unknown): string {
if (error instanceof ApiError) {
switch (error.status) {
case 401: return 'Please log in to continue.';
case 403: return 'You don\'t have permission to do this.';
case 404: return 'The item you\'re looking for doesn\'t exist.';
case 409: return 'This conflicts with an existing item.';
case 422:
const fields = error.body?.errors;
if (fields?.length) return fields.map((f: any) => f.message).join('. ');
return 'Please check your input.';
case 429: return 'Too many requests. Please wait a moment.';
default: return 'Something went wrong. Please try again.';
}
}
if (error instanceof TypeError && error.message === 'Failed to fetch') {
return 'Cannot connect to server. Check your internet connection.';
}
return 'An unexpected error occurred.';
}const queryClient = new QueryClient({
defaultOptions: {
mutations: { onError: (error) => toast.error(getErrorMessage(error)) },
queries: {
retry: (failureCount, error) => {
if (error instanceof ApiError && error.status < 500) return false;
return failureCount < 3;
},
},
},
});✅ Map every API error code to a human-readable message
✅ Show field-level validation errors next to form inputs
✅ Auto-retry on 5xx (max 3, with backoff), never on 4xx
✅ Redirect to login on 401 (after refresh attempt fails)
✅ Show "offline" banner when fetch fails with TypeError
❌ Never show raw API error messages to users ("NullPointerException")
❌ Never silently swallow errors (show toast or log)
❌ Never retry 4xx errors (client is wrong, retrying won't help)Same team owns frontend + backend?
│
├─ YES, both TypeScript
│ └─ tRPC (end-to-end type safety, zero codegen)
│
├─ YES, different languages
│ └─ OpenAPI spec → generated client (type safety via codegen)
│
├─ NO, public API
│ └─ REST + OpenAPI → generated SDKs for consumers
│
└─ Complex data needs, multiple frontends
└─ GraphQL + codegen (flexible queries per client)
Real-time needed?
│
├─ Server → Client only (notifications, feeds, AI streaming)
│ └─ SSE (simplest, auto-reconnect, works through proxies)
│
├─ Bidirectional (chat, collaboration)
│ └─ WebSocket (need heartbeat + reconnection logic)
│
└─ Simple status polling (< 10 clients)
└─ React Query refetchInterval (no infrastructure needed)app.get('/health', (req, res) => res.json({ status: 'ok' })); // liveness
app.get('/ready', async (req, res) => { // readiness
const checks = {
database: await checkDb(), redis: await checkRedis(),
};
const ok = Object.values(checks).every(c => c.status === 'ok');
res.status(ok ? 200 : 503).json({ status: ok ? 'ok' : 'degraded', checks });
});process.on('SIGTERM', async () => {
logger.info('SIGTERM received');
server.close(); // stop new connections
await drainConnections(); // finish in-flight
await closeDatabase();
process.exit(0);
});✅ CORS: explicit origins (never '*' in production)
✅ Security headers (helmet / equivalent)
✅ Rate limiting on public endpoints
✅ Input validation on ALL endpoints (trust nothing)
✅ HTTPS enforced
❌ Never expose internal errors to clients| # | ❌ Don't | ✅ Do Instead |
|---|---|---|
| 1 | Business logic in routes/controllers | Move to service layer |
| 2 | process.env scattered everywhere | Centralized typed config |
| 3 | console.log for logging | Structured JSON logger |
| 4 | Generic Error('oops') | Typed error hierarchy |
| 5 | Direct DB calls in controllers | Repository pattern |
| 6 | No input validation | Validate at boundary (Zod/Pydantic) |
| 7 | Catching errors silently | Log + rethrow or return error |
| 8 | No health check endpoints | /health + /ready |
| 9 | Hardcoded config/secrets | Environment variables |
| 10 | No graceful shutdown | Handle SIGTERM properly |
| 11 | Hardcode API URL in frontend | Environment variable (NEXT_PUBLIC_API_URL) |
| 12 | Store JWT in localStorage | Memory + httpOnly refresh cookie |
| 13 | Show raw API errors to users | Map to human-readable messages |
| 14 | Retry 4xx errors | Only retry 5xx (server failures) |
| 15 | Skip loading states | Skeleton/spinner while fetching |
| 16 | Upload large files through API server | Presigned URL → direct to S3 |
| 17 | Poll for real-time data | SSE or WebSocket |
| 18 | Duplicate types frontend + backend | Shared types, tRPC, or OpenAPI codegen |
Rule: If it involves HTTP (request parsing, status codes, headers) → controller. If it involves business decisions (pricing, permissions, rules) → service. If it touches the database → repository.
Symptom: One service file > 500 lines with 20+ methods.
Fix: Split by sub-domain. OrderService → OrderCreationService + OrderFulfillmentService + OrderQueryService. Each focused on one workflow.
Fix: Unit tests mock the repository layer (fast). Integration tests use test containers or transaction rollback (real DB, still fast). Never mock the service layer in integration tests.
This skill includes deep-dive references for specialized topics. Read the relevant reference when you need detailed guidance.
| Need to… | Reference |
|---|---|
| Write backend tests (unit, integration, e2e, contract, performance) | references/testing-strategy.md |
| Validate a release before deployment (6-gate checklist) | references/release-checklist.md |
| Choose a tech stack (language, framework, database, infra) | references/technology-selection.md |
| Build with Django / DRF (models, views, serializers, admin) | references/django-best-practices.md |
| Design REST/GraphQL/gRPC endpoints (URLs, status codes, pagination) | references/api-design.md |
| Design database schema, indexes, migrations, multi-tenancy | references/db-schema.md |
| Auth flow (JWT bearer, token refresh, Next.js SSR, RBAC, middleware order) | references/auth-flow.md |
| CORS config, env vars per environment, common CORS issues | references/environment-management.md |
© HHU3637kr, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 8 other files (references) in fullstack-dev of HHU3637kr/skills.
Open the folder on GitHubat commit a2cccf6
We found 8 copies of this SKILL.md (exact, near-identical or edited) in other folders, from 3 other GitHub owners. This page covers the copy in HHU3637kr/skills, which our catalogue first saw on October 7, 2026.
Fullstack Dev next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Fullstack Dev this skillHHU3637kr/skills | 145 | 3 repos | ~8.6k | Automated safety check: Notes | MIT | |
| Fullstack Devinfometa/workbuddyskills | 342 | — | ~1k | Automated safety check: Pass | MIT | |
| Vercel Functionsvercel/vercel-plugin | 301 | — | ~12k | Automated safety check: Notes | Custom licence | |
| Clerk Setupgrowupanand/ConvoForm | 101 | — | ~3k | Automated safety check: Pass | MIT | |
| Documentation LookupKaimingWan/oh-my-kiro | 107 | — | ~616 | Automated safety check: Pass | MIT | |
| FastAPI ExpertJeffallan/claude-skills | 12k | — | ~1.8k | Automated safety check: Pass | MIT |
infometa/workbuddyskills
Full-stack backend architecture and frontend-backend integration guide.
vercel/vercel-plugin
Vercel Functions expert guidance — Node.js/Bun/Python runtimes, Fluid Compute, long-duration (30 min) functions, large functions (5 GB bundles), Docker/OCI container images, plan limits, streaming…
growupanand/ConvoForm
Set up Clerk authentication in any project with the Clerk CLI and official framework quickstarts.
KaimingWan/oh-my-kiro
Fetch current library/framework documentation via Context7. An agent skill from KaimingWan/oh-my-kiro.
Jeffallan/claude-skills
Builds async Python APIs with FastAPI and Pydantic V2, covering endpoints, JWT authentication, async SQLAlchemy, WebSockets and pytest checks against the OpenAPI docs.
davila7/claude-code-templates
Comprehensive backend development skill for building scalable backend systems using NodeJS, Express, Go, Python, Postgres, GraphQL, REST APIs.
HHU3637kr/skills
Convert photos (people, pets, objects, logos) into 4 animated GIF stickers with captions.
HHU3637kr/skills
Headless browser automation for AI agents using agent-browser CLI.
HHU3637kr/skills
Darwin Skill (达尔文.skill): autonomous skill optimizer inspired by Karpathy's autoresearch.
HHU3637kr/skills
Cross-platform token audit and optimization for AI coding agents (Cursor, Codex, Claude Code, Hermes, Windsurf, Cline, OpenClaw, Gemini CLI, Aider).
HHU3637kr/skills
生成横向翻页网页 PPT(单 HTML 文件),含 WebGL 背景、章节幕封、数据大字报、图片网格等模板。提供两种风格:① "电子杂志 × 电子墨水"(衬线 + 流体背景 + 暖色) ② "瑞士国际主义"(无衬线 + 网格点阵 + IKB/柠檬黄/柠檬绿/安全橙高亮)。当用户需要制作分享 / 演讲 / 发布会风格的网页 PPT,或提到"杂志风 PPT"、"瑞士风 PPT"、"Swiss…
HHU3637kr/skills
女娲造人:输入人名/主题/甚至只是模糊需求,自动深度调研→思维框架提炼→生成可运行的人物Skill. An agent skill from HHU3637kr/skills.
Categories
Full-stack backend architecture and frontend-backend integration guide. Fullstack Dev is an agent skill from HHU3637kr/skills. Full-stack backend architecture and frontend-backend integration guide.
Fullstack Dev fits situations like: : building a full-stack app; creating REST API with frontend; scaffolding backend service; building todo app.
Run `npx skills add HHU3637kr/skills --skill fullstack-dev -a claude-code`. Or copy the skill folder (fullstack-dev in HHU3637kr/skills) into .claude/skills/fullstack-dev in your project. Claude Code loads it when a task matches its description.
Run `npx skills add HHU3637kr/skills --skill fullstack-dev -a codex`. Or copy the skill folder (fullstack-dev in HHU3637kr/skills) into .agents/skills/fullstack-dev in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add HHU3637kr/skills --skill fullstack-dev -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/fullstack-dev, .gemini/skills/fullstack-dev, .github/skills/fullstack-dev and .opencode/skills/fullstack-dev in your project.
Going by SKILL.md and its folder, Fullstack Dev needs the command-line tools its instructions call (npx, npm and curl) and credentials named JWT_SECRET. Our summary lists: Python 3; Node.js; A credential in JWT_SECRET.
SKILL.md contains no URLs. Its commands use npx, npm and curl, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Fullstack Dev is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 8.6k tokens (SKILL.md is roughly 35k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 22k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Fullstack Dev: Fullstack Dev (infometa/workbuddyskills, 342 stars), Vercel Functions (vercel/vercel-plugin, 301 stars), Clerk Setup (growupanand/ConvoForm, 101 stars) and Documentation Lookup (KaimingWan/oh-my-kiro, 107 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
HHU3637kr (a GitHub user) maintains it in HHU3637kr/skills, which has 145 GitHub stars. The repository holds 37 skills in this directory. The repository was last updated on September 23, 2026.
Source: HHU3637kr/skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.