fullstack-dev
Full-stack development best practices, standardized workflow from requirements gathering to project scaffolding. AI Agent Skill to improve work efficiency and automation capabilities.
npx skills add minimax-ai/skills --skill fullstack-devBefore / After Comparison
1 组Manually selecting the technology stack, configuring the development environment, and setting up the project structure often leads to missing critical configurations. Starting a project takes 1-2 days.
Following standardized procedures to gather requirements, select the stack, and generate scaffolding, project initialization and configuration can be completed in 2 hours.
fullstack-dev
Full-Stack Development Practices
MANDATORY WORKFLOW — Follow These Steps In Order
When this skill is triggered, you MUST follow this workflow before writing any code.
Step 0: Gather Requirements
Before scaffolding anything, ask the user to clarify (or infer from context):
-
Stack: Language/framework for backend and frontend (e.g., Express + React, Django + Vue, Go + HTMX)
-
Service type: API-only, full-stack monolith, or microservice?
-
Database: SQL (PostgreSQL, SQLite, MySQL) or NoSQL (MongoDB, Redis)?
-
Integration: REST, GraphQL, tRPC, or gRPC?
-
Real-time: Needed? If yes — SSE, WebSocket, or polling?
-
Auth: Needed? If yes — JWT, session, OAuth, or third-party (Clerk, Auth.js)?
If the user has already specified these in their request, skip asking and proceed.
Step 1: Architectural Decisions
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).
Step 2: Scaffold with Checklist
Use the appropriate checklist below. Ensure ALL checked items are implemented — do not skip any.
Step 3: Implement Following Patterns
Write code following the patterns in this document. Reference specific sections as you implement each part.
Step 4: Test & Verify
After implementation, run these checks before claiming completion:
- Build check: Ensure both backend and frontend compile without errors
# Backend
cd server && npm run build
# Frontend
cd client && npm run build
- Start & smoke test: Start the server, verify key endpoints return expected responses
# Start server, then test
curl http://localhost:3000/health
curl http://localhost:3000/api/<resource>
-
Integration check: Verify frontend can connect to backend (CORS, API base URL, auth flow)
-
Real-time check (if applicable): Open two browser tabs, verify changes sync
If any check fails, fix the issue before proceeding.
Step 5: Handoff Summary
Provide a brief summary to the user:
-
What was built: List of implemented features and endpoints
-
How to run: Exact commands to start backend and frontend
-
What's missing / next steps: Any deferred items, known limitations, or recommended improvements
-
Key files: List the most important files the user should know about
Scope
USE this skill when:
-
Building a full-stack application (backend + frontend)
-
Scaffolding a new backend service or API
-
Designing service layers and module boundaries
-
Implementing database access, caching, or background jobs
-
Writing error handling, logging, or configuration management
-
Reviewing backend code for architectural issues
-
Hardening for production
-
Setting up API clients, auth flows, file uploads, or real-time features
NOT for:
-
Pure frontend/UI concerns (use your frontend framework's docs)
-
Pure database schema design without backend context
Quick Start — New Backend Service Checklist
-
Project scaffolded with feature-first structure
-
Configuration centralized, env vars validated at startup (fail fast)
-
Typed error hierarchy defined (not generic
Error) -
Global error handler middleware
-
Structured JSON logging with request ID propagation
-
Database: migrations set up, connection pooling configured
-
Input validation on all endpoints (Zod / Pydantic / Go validator)
-
Authentication middleware in place
-
Health check endpoints (
/health,/ready) -
Graceful shutdown handling (SIGTERM)
-
CORS configured (explicit origins, not
*) -
Security headers (helmet or equivalent)
-
.env.examplecommitted (no real secrets)
Quick Start — Frontend-Backend Integration Checklist
-
API client configured (typed fetch wrapper, React Query, tRPC, or OpenAPI generated)
-
Base URL from environment variable (not hardcoded)
-
Auth token attached to requests automatically (interceptor / middleware)
-
Error handling — API errors mapped to user-facing messages
-
Loading states handled (skeleton/spinner, not blank screen)
-
Type safety across the boundary (shared types, OpenAPI, or tRPC)
-
CORS configured with explicit origins (not
*in production) -
Refresh token flow implemented (httpOnly cookie + transparent retry on 401)
Quick Navigation
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
Core Principles (7 Iron Rules)
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
1. Project Structure & Layering (CRITICAL)
Feature-First Organization
✅ 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/
Three-Layer Architecture
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
Dependency Injection (All Languages)
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_service
Go:
type OrderService struct {
orderRepo OrderRepository // ✅ interface
emailService EmailService
}
func NewOrderService(repo OrderRepository, email EmailService) *OrderService {
return &OrderService{orderRepo: repo, emailService: email}
}
2. Configuration & Environment (CRITICAL)
Centralized, Typed, Fail-Fast
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
Rules
✅ 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
3. Error Handling & Resilience (HIGH)
Typed Error Hierarchy
// 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)
Global Error Handler
// 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 });
});
Rules
✅ 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')
4. Database Access Patterns (HIGH)
Migrations Always
# 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 Prevention
// ❌ 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 } });
Transactions for Multi-Step Writes
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 });
});
Connection Pooling
Pool size = (CPU cores × 2) + spindle_count (start with 10-20). Always set connection timeout. Use PgBouncer for serverless.
5. API Client Patterns (MEDIUM)
The "glue layer" between frontend and backend. Choose the approach that fits your team and stack.
Option A: Typed Fetch Wrapper (Simple, No Dependencies)
// 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' }),
};
Option B: React Query + Typed Client (Recommended for React)
// hooks/use-orders.ts
import {
...
User Reviews (0)
Write a Review
No reviews yet
Statistics
User Rating
Rate this Skill