Senior Fullstack Engineer
End-to-end project scaffolding with Next.js, FastAPI, MERN, and Django — full-stack code quality analysis and frontend-backend integration.
What this skill does
Launch new web projects instantly with fully configured foundations that save hours of manual setup. You can also analyze your project to uncover security risks and ensure high quality standards. Use this whenever you need to start building from scratch or validate the health of your current software.
name: “senior-fullstack” description: Fullstack development toolkit with project scaffolding for Next.js, FastAPI, MERN, and Django stacks, code quality analysis with security and complexity scoring, and stack selection guidance. Use when the user asks to “scaffold a new project”, “create a Next.js app”, “set up FastAPI with React”, “analyze code quality”, “audit my codebase”, “what stack should I use”, “generate project boilerplate”, or mentions fullstack development, project setup, or tech stack comparison.
Senior Fullstack
Fullstack development skill with project scaffolding and code quality analysis tools.
Table of Contents
Trigger Phrases
Use this skill when you hear:
- “scaffold a new project”
- “create a Next.js app”
- “set up FastAPI with React”
- “analyze code quality”
- “check for security issues in codebase”
- “what stack should I use”
- “set up a fullstack project”
- “generate project boilerplate”
Tools
Project Scaffolder
Generates fullstack project structures with boilerplate code.
Supported Templates:
nextjs- Next.js 14+ with App Router, TypeScript, Tailwind CSSfastapi-react- FastAPI backend + React frontend + PostgreSQLmern- MongoDB, Express, React, Node.js with TypeScriptdjango-react- Django REST Framework + React frontend
Usage:
# List available templates
python scripts/project_scaffolder.py --list-templates
# Create Next.js project
python scripts/project_scaffolder.py nextjs my-app
# Create FastAPI + React project
python scripts/project_scaffolder.py fastapi-react my-api
# Create MERN stack project
python scripts/project_scaffolder.py mern my-project
# Create Django + React project
python scripts/project_scaffolder.py django-react my-app
# Specify output directory
python scripts/project_scaffolder.py nextjs my-app --output ./projects
# JSON output
python scripts/project_scaffolder.py nextjs my-app --json
Parameters:
| Parameter | Description |
|---|---|
template | Template name (nextjs, fastapi-react, mern, django-react) |
project_name | Name for the new project directory |
--output, -o | Output directory (default: current directory) |
--list-templates, -l | List all available templates |
--json | Output in JSON format |
Output includes:
- Project structure with all necessary files
- Package configurations (package.json, requirements.txt)
- TypeScript configuration
- Docker and docker-compose setup
- Environment file templates
- Next steps for running the project
Code Quality Analyzer
Analyzes fullstack codebases for quality issues.
Analysis Categories:
- Security vulnerabilities (hardcoded secrets, injection risks)
- Code complexity metrics (cyclomatic complexity, nesting depth)
- Dependency health (outdated packages, known CVEs)
- Test coverage estimation
- Documentation quality
Usage:
# Analyze current directory
python scripts/code_quality_analyzer.py .
# Analyze specific project
python scripts/code_quality_analyzer.py /path/to/project
# Verbose output with detailed findings
python scripts/code_quality_analyzer.py . --verbose
# JSON output
python scripts/code_quality_analyzer.py . --json
# Save report to file
python scripts/code_quality_analyzer.py . --output report.json
Parameters:
| Parameter | Description |
|---|---|
project_path | Path to project directory (default: current directory) |
--verbose, -v | Show detailed findings |
--json | Output in JSON format |
--output, -o | Write report to file |
Output includes:
- Overall score (0-100) with letter grade
- Security issues by severity (critical, high, medium, low)
- High complexity files
- Vulnerable dependencies with CVE references
- Test coverage estimate
- Documentation completeness
- Prioritized recommendations
Sample Output:
============================================================
CODE QUALITY ANALYSIS REPORT
============================================================
Overall Score: 75/100 (Grade: C)
Files Analyzed: 45
Total Lines: 12,500
--- SECURITY ---
Critical: 1
High: 2
Medium: 5
--- COMPLEXITY ---
Average Complexity: 8.5
High Complexity Files: 3
--- RECOMMENDATIONS ---
1. [P0] SECURITY
Issue: Potential hardcoded secret detected
Action: Remove or secure sensitive data at line 42
Workflows
Workflow 1: Start New Project
- Choose appropriate stack based on requirements (see Stack Decision Matrix)
- Scaffold project structure
- Verify scaffold: confirm
package.json(orrequirements.txt) exists - Run initial quality check — address any P0 issues before proceeding
- Set up development environment
# 1. Scaffold project
python scripts/project_scaffolder.py nextjs my-saas-app
# 2. Verify scaffold succeeded
ls my-saas-app/package.json
# 3. Navigate and install
cd my-saas-app
npm install
# 4. Configure environment
cp .env.example .env.local
# 5. Run quality check
python ../scripts/code_quality_analyzer.py .
# 6. Start development
npm run dev
Workflow 2: Audit Existing Codebase
- Run code quality analysis
- Review security findings — fix all P0 (critical) issues immediately
- Re-run analyzer to confirm P0 issues are resolved
- Create tickets for P1/P2 issues
# 1. Full analysis
python scripts/code_quality_analyzer.py /path/to/project --verbose
# 2. Generate detailed report
python scripts/code_quality_analyzer.py /path/to/project --json --output audit.json
# 3. After fixing P0 issues, re-run to verify
python scripts/code_quality_analyzer.py /path/to/project --verbose
Workflow 3: Stack Selection
Use the tech stack guide to evaluate options:
- SEO Required? → Next.js with SSR
- API-heavy backend? → Separate FastAPI or NestJS
- Real-time features? → Add WebSocket layer
- Team expertise → Match stack to team skills
See references/tech_stack_guide.md for detailed comparison.
Reference Guides
Architecture Patterns (references/architecture_patterns.md)
- Frontend component architecture (Atomic Design, Container/Presentational)
- Backend patterns (Clean Architecture, Repository Pattern)
- API design (REST conventions, GraphQL schema design)
- Database patterns (connection pooling, transactions, read replicas)
- Caching strategies (cache-aside, HTTP cache headers)
- Authentication architecture (JWT + refresh tokens, sessions)
Development Workflows (references/development_workflows.md)
- Local development setup (Docker Compose, environment config)
- Git workflows (trunk-based, conventional commits)
- CI/CD pipelines (GitHub Actions examples)
- Testing strategies (unit, integration, E2E)
- Code review process (PR templates, checklists)
- Deployment strategies (blue-green, canary, feature flags)
- Monitoring and observability (logging, metrics, health checks)
Tech Stack Guide (references/tech_stack_guide.md)
- Frontend frameworks comparison (Next.js, React+Vite, Vue)
- Backend frameworks (Express, Fastify, NestJS, FastAPI, Django)
- Database selection (PostgreSQL, MongoDB, Redis)
- ORMs (Prisma, Drizzle, SQLAlchemy)
- Authentication solutions (Auth.js, Clerk, custom JWT)
- Deployment platforms (Vercel, Railway, AWS)
- Stack recommendations by use case (MVP, SaaS, Enterprise)
Quick Reference
Stack Decision Matrix
| Requirement | Recommendation |
|---|---|
| SEO-critical site | Next.js with SSR |
| Internal dashboard | React + Vite |
| API-first backend | FastAPI or Fastify |
| Enterprise scale | NestJS + PostgreSQL |
| Rapid prototype | Next.js API routes |
| Document-heavy data | MongoDB |
| Complex queries | PostgreSQL |
Common Issues
| Issue | Solution |
|---|---|
| N+1 queries | Use DataLoader or eager loading |
| Slow builds | Check bundle size, lazy load |
| Auth complexity | Use Auth.js or Clerk |
| Type errors | Enable strict mode in tsconfig |
| CORS issues | Configure middleware properly |
Fullstack Architecture Patterns
Proven architectural patterns for scalable fullstack applications covering frontend, backend, and their integration.
Table of Contents
- Frontend Architecture
- Backend Architecture
- API Design Patterns
- Database Patterns
- Caching Strategies
- Authentication Architecture
Frontend Architecture
Component Architecture
Atomic Design Pattern
Organize components in hierarchical levels:
src/components/
├── atoms/ # Button, Input, Icon
├── molecules/ # SearchInput, FormField
├── organisms/ # Header, Footer, Sidebar
├── templates/ # PageLayout, DashboardLayout
└── pages/ # Home, Profile, SettingsWhen to use: Large applications with design systems and multiple teams.
Container/Presentational Pattern
// Presentational - pure rendering, no state
function UserCard({ name, email, avatar }: UserCardProps) {
return (
<div className="card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);
}
// Container - handles data fetching and state
function UserCardContainer({ userId }: { userId: string }) {
const { data, loading } = useUser(userId);
if (loading) return <Skeleton />;
return <UserCard {...data} />;
}When to use: When you need clear separation between UI and logic.
State Management Patterns
Server State vs Client State
| Type | Examples | Tools |
|---|---|---|
| Server State | User data, API responses | React Query, SWR |
| Client State | UI toggles, form inputs | Zustand, Jotai |
| URL State | Filters, pagination | Next.js router |
React Query for Server State:
function useUsers(filters: Filters) {
return useQuery({
queryKey: ["users", filters],
queryFn: () => api.getUsers(filters),
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 30 * 60 * 1000, // 30 minutes
});
}
// Mutations with optimistic updates
function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: api.updateUser,
onMutate: async (newUser) => {
await queryClient.cancelQueries({ queryKey: ["users"] });
const previous = queryClient.getQueryData(["users"]);
queryClient.setQueryData(["users"], (old) =>
old.map(u => u.id === newUser.id ? newUser : u)
);
return { previous };
},
onError: (err, newUser, context) => {
queryClient.setQueryData(["users"], context.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
}Backend Architecture
Clean Architecture
src/
├── domain/ # Business entities, no dependencies
│ ├── entities/ # User, Order, Product
│ └── interfaces/ # Repository interfaces
├── application/ # Use cases, application logic
│ ├── use-cases/ # CreateOrder, UpdateUser
│ └── services/ # OrderService, AuthService
├── infrastructure/ # External concerns
│ ├── database/ # Repository implementations
│ ├── http/ # Controllers, middleware
│ └── external/ # Third-party integrations
└── shared/ # Cross-cutting concerns
├── errors/
└── utils/Dependency Flow: domain ← application ← infrastructure
Repository Pattern:
// Domain interface
interface UserRepository {
findById(id: string): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
save(user: User): Promise<User>;
delete(id: string): Promise<void>;
}
// Infrastructure implementation
class PostgresUserRepository implements UserRepository {
constructor(private db: Database) {}
async findById(id: string): Promise<User | null> {
const row = await this.db.query(
"SELECT * FROM users WHERE id = $1",
[id]
);
return row ? this.toEntity(row) : null;
}
private toEntity(row: UserRow): User {
return new User({
id: row.id,
email: row.email,
name: row.name,
createdAt: row.created_at,
});
}
}Middleware Pipeline
// Express middleware chain
app.use(cors());
app.use(helmet());
app.use(requestId());
app.use(logger());
app.use(authenticate());
app.use(rateLimit());
app.use("/api", routes);
app.use(errorHandler());
// Custom middleware example
function requestId() {
return (req: Request, res: Response, next: NextFunction) => {
req.id = req.headers["x-request-id"] || crypto.randomUUID();
res.setHeader("x-request-id", req.id);
next();
};
}
function errorHandler() {
return (err: Error, req: Request, res: Response, next: NextFunction) => {
const status = err instanceof AppError ? err.status : 500;
const message = status === 500 ? "Internal Server Error" : err.message;
logger.error({ err, requestId: req.id });
res.status(status).json({ error: message, requestId: req.id });
};
}API Design Patterns
REST Best Practices
Resource Naming:
- Use nouns, not verbs:
/usersnot/getUsers - Use plural:
/usersnot/user - Nest for relationships:
/users/{id}/orders
HTTP Methods:
| Method | Purpose | Idempotent |
|---|---|---|
| GET | Retrieve | Yes |
| POST | Create | No |
| PUT | Replace | Yes |
| PATCH | Partial update | No |
| DELETE | Remove | Yes |
Response Envelope:
// Success response
{
"data": { /* resource */ },
"meta": {
"requestId": "abc-123",
"timestamp": "2024-01-15T10:30:00Z"
}
}
// Paginated response
{
"data": [/* items */],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 150,
"totalPages": 8
}
}
// Error response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{ "field": "email", "message": "Invalid email format" }
]
},
"meta": { "requestId": "abc-123" }
}GraphQL Architecture
Schema-First Design:
type Query {
user(id: ID!): User
users(filter: UserFilter, page: PageInput): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): UserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UserPayload!
}
type User {
id: ID!
email: String!
profile: Profile
orders(first: Int, after: String): OrderConnection!
}
type UserPayload {
user: User
errors: [Error!]
}Resolver Pattern:
const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
return dataSources.userAPI.findById(id);
},
},
User: {
// Field resolver for related data
orders: async (user, { first, after }, { dataSources }) => {
return dataSources.orderAPI.findByUserId(user.id, { first, after });
},
},
};DataLoader for N+1 Prevention:
const userLoader = new DataLoader(async (userIds: string[]) => {
const users = await db.query(
"SELECT * FROM users WHERE id = ANY($1)",
[userIds]
);
// Return in same order as input
return userIds.map(id => users.find(u => u.id === id));
});Database Patterns
Connection Pooling
// PostgreSQL with connection pool
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20, // Maximum connections
idleTimeoutMillis: 30000, // Close idle connections
connectionTimeoutMillis: 2000,
});
// Prisma with connection pool
const prisma = new PrismaClient({
datasources: {
db: {
url: `${process.env.DATABASE_URL}?connection_limit=20&pool_timeout=10`,
},
},
});Transaction Patterns
// Unit of Work pattern
async function transferFunds(from: string, to: string, amount: number) {
return await prisma.$transaction(async (tx) => {
const sender = await tx.account.update({
where: { id: from },
data: { balance: { decrement: amount } },
});
if (sender.balance < 0) {
throw new InsufficientFundsError();
}
await tx.account.update({
where: { id: to },
data: { balance: { increment: amount } },
});
return tx.transaction.create({
data: { fromId: from, toId: to, amount },
});
});
}Read Replicas
// Route reads to replica
const readDB = new PrismaClient({
datasources: { db: { url: process.env.READ_DATABASE_URL } },
});
const writeDB = new PrismaClient({
datasources: { db: { url: process.env.WRITE_DATABASE_URL } },
});
class UserRepository {
async findById(id: string) {
return readDB.user.findUnique({ where: { id } });
}
async create(data: CreateUserData) {
return writeDB.user.create({ data });
}
}Caching Strategies
Cache Layers
Request → CDN Cache → Application Cache → Database Cache → DatabaseCache-Aside Pattern:
async function getUser(id: string): Promise<User> {
const cacheKey = `user:${id}`;
// 1. Try cache
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 2. Fetch from database
const user = await db.user.findUnique({ where: { id } });
if (!user) throw new NotFoundError();
// 3. Store in cache
await redis.set(cacheKey, JSON.stringify(user), "EX", 3600);
return user;
}
// Invalidate on update
async function updateUser(id: string, data: UpdateData): Promise<User> {
const user = await db.user.update({ where: { id }, data });
await redis.del(`user:${id}`);
return user;
}HTTP Cache Headers:
// Immutable assets (hashed filenames)
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
// API responses
res.setHeader("Cache-Control", "private, max-age=0, must-revalidate");
res.setHeader("ETag", generateETag(data));
// Static pages
res.setHeader("Cache-Control", "public, max-age=3600, stale-while-revalidate=86400");Authentication Architecture
JWT + Refresh Token Flow
1. User logs in → Server returns access token (15min) + refresh token (7d)
2. Client stores tokens (httpOnly cookie for refresh, memory for access)
3. Access token expires → Client uses refresh token to get new pair
4. Refresh token expires → User must log in againImplementation:
// Token generation
function generateTokens(user: User) {
const accessToken = jwt.sign(
{ sub: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: "15m" }
);
const refreshToken = jwt.sign(
{ sub: user.id, tokenVersion: user.tokenVersion },
process.env.REFRESH_SECRET,
{ expiresIn: "7d" }
);
return { accessToken, refreshToken };
}
// Refresh endpoint
app.post("/auth/refresh", async (req, res) => {
const refreshToken = req.cookies.refreshToken;
try {
const payload = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
const user = await db.user.findUnique({ where: { id: payload.sub } });
// Check token version (invalidation mechanism)
if (user.tokenVersion !== payload.tokenVersion) {
throw new Error("Token revoked");
}
const tokens = generateTokens(user);
setRefreshCookie(res, tokens.refreshToken);
res.json({ accessToken: tokens.accessToken });
} catch {
res.status(401).json({ error: "Invalid refresh token" });
}
});Session-Based Auth
// Redis session store
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production",
httpOnly: true,
sameSite: "lax",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
},
}));
// Login
app.post("/auth/login", async (req, res) => {
const user = await authenticate(req.body.email, req.body.password);
req.session.userId = user.id;
res.json({ user });
});
// Middleware
function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: "Authentication required" });
}
next();
}Decision Matrix
| Pattern | Complexity | Scalability | When to Use |
|---|---|---|---|
| Monolith | Low | Medium | MVPs, small teams |
| Modular Monolith | Medium | High | Growing teams |
| Microservices | High | Very High | Large orgs, diverse tech |
| REST | Low | High | CRUD APIs, public APIs |
| GraphQL | Medium | High | Complex data needs, mobile apps |
| JWT Auth | Low | High | Stateless APIs, microservices |
| Session Auth | Low | Medium | Traditional web apps |
Fullstack Development Workflows
Complete development lifecycle workflows from local setup to production deployment.
Table of Contents
- Local Development Setup
- Git Workflows
- CI/CD Pipelines
- Testing Strategies
- Code Review Process
- Deployment Strategies
- Monitoring and Observability
Local Development Setup
Docker Compose Development Environment
# docker-compose.yml
version: "3.8"
services:
app:
build:
context: .
target: development
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/app
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:Multistage Dockerfile:
# Base stage
FROM node:20-alpine AS base
WORKDIR /app
RUN apk add --no-cache libc6-compat
# Development stage
FROM base AS development
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
# Builder stage
FROM base AS builder
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM base AS production
ENV NODE_ENV=production
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
USER node
CMD ["node", "dist/index.js"]Environment Configuration
# .env.local (development)
DATABASE_URL="postgresql://user:pass@localhost:5432/app_dev"
REDIS_URL="redis://localhost:6379"
JWT_SECRET="development-secret-change-in-prod"
LOG_LEVEL="debug"
# .env.test
DATABASE_URL="postgresql://user:pass@localhost:5432/app_test"
LOG_LEVEL="error"
# .env.production (via secrets management)
DATABASE_URL="${DATABASE_URL}"
REDIS_URL="${REDIS_URL}"
JWT_SECRET="${JWT_SECRET}"Environment validation:
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url().optional(),
JWT_SECRET: z.string().min(32),
PORT: z.coerce.number().default(3000),
});
export const env = envSchema.parse(process.env);Git Workflows
Trunk-Based Development
main (protected)
│
├── feature/user-auth (short-lived, 1-2 days max)
│ └── squash merge → main
│
├── feature/payment-flow
│ └── squash merge → main
│
└── release/v1.2.0 (cut from main for hotfixes)Branch naming:
feature/description- New featuresfix/description- Bug fixeschore/description- Maintenance tasksrelease/vX.Y.Z- Release branches
Commit Standards
Conventional Commits:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]Types:
feat: New featurefix: Bug fixdocs: Documentationstyle: Formattingrefactor: Code restructuringtest: Adding testschore: Maintenance
Examples:
feat(auth): add password reset flow
Implement password reset with email verification.
Tokens expire after 1 hour.
Closes #123
---
fix(api): handle null response in user endpoint
The API was returning 500 when user profile was incomplete.
Now returns partial data with null fields.
---
chore(deps): update Next.js to 14.1.0
Breaking changes addressed:
- Updated Image component usage
- Migrated to new metadata APIPre-commit Hooks
// package.json
{
"scripts": {
"prepare": "husky install"
},
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
}# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
# .husky/commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx commitlint --edit $1CI/CD Pipelines
GitHub Actions
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run type-check
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run test:unit
- run: npm run test:integration
env:
DATABASE_URL: postgresql://test:test@localhost:5432/test
- uses: codecov/codecov-action@v3
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/
deploy-preview:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build
path: dist/
# Deploy to preview environment
- name: Deploy Preview
run: |
# Deploy logic here
echo "Deployed to preview-${{ github.event.pull_request.number }}"
deploy-production:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: build
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build
path: dist/
- name: Deploy Production
run: |
# Production deployment
echo "Deployed to production"Database Migrations in CI
# Part of deploy job
- name: Run Migrations
run: |
npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- name: Verify Migration
run: |
npx prisma migrate statusTesting Strategies
Testing Pyramid
/\
/ \ E2E Tests (10%)
/ \ - Critical user journeys
/──────\
/ \ Integration Tests (20%)
/ \ - API endpoints
/────────────\ - Database operations
/ \
/ \ Unit Tests (70%)
/──────────────────\ - Components, hooks, utilitiesUnit Testing
// Component test with React Testing Library
import { render, screen, fireEvent } from "@testing-library/react";
import { UserForm } from "./UserForm";
describe("UserForm", () => {
it("submits form with valid data", async () => {
const onSubmit = vi.fn();
render(<UserForm onSubmit={onSubmit} />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: "[email protected]" },
});
fireEvent.change(screen.getByLabelText(/name/i), {
target: { value: "John Doe" },
});
fireEvent.click(screen.getByRole("button", { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: "[email protected]",
name: "John Doe",
});
});
});
it("shows validation error for invalid email", async () => {
render(<UserForm onSubmit={vi.fn()} />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: "invalid" },
});
fireEvent.click(screen.getByRole("button", { name: /submit/i }));
expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();
});
});Integration Testing
// API integration test
import { createTestClient } from "./test-utils";
import { db } from "@/lib/db";
describe("POST /api/users", () => {
beforeEach(async () => {
await db.user.deleteMany();
});
it("creates user with valid data", async () => {
const client = createTestClient();
const response = await client.post("/api/users", {
email: "[email protected]",
name: "New User",
});
expect(response.status).toBe(201);
expect(response.data.user.email).toBe("[email protected]");
// Verify in database
const user = await db.user.findUnique({
where: { email: "[email protected]" },
});
expect(user).toBeTruthy();
});
it("returns 409 for duplicate email", async () => {
await db.user.create({
data: { email: "[email protected]", name: "Existing" },
});
const client = createTestClient();
const response = await client.post("/api/users", {
email: "[email protected]",
name: "Duplicate",
});
expect(response.status).toBe(409);
expect(response.data.error.code).toBe("EMAIL_EXISTS");
});
});E2E Testing with Playwright
// e2e/auth.spec.ts
import { test, expect } from "@playwright/test";
test.describe("Authentication", () => {
test("user can log in and access dashboard", async ({ page }) => {
await page.goto("/login");
await page.fill('[name="email"]', "[email protected]");
await page.fill('[name="password"]', "password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/dashboard");
await expect(page.locator("h1")).toHaveText("Welcome back");
});
test("shows error for invalid credentials", async ({ page }) => {
await page.goto("/login");
await page.fill('[name="email"]', "[email protected]");
await page.fill('[name="password"]', "wrongpassword");
await page.click('button[type="submit"]');
await expect(page.locator('[role="alert"]')).toHaveText(
"Invalid email or password"
);
});
});Code Review Process
PR Template
## Summary
<!-- Brief description of changes -->
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Changes Made
<!-- List specific changes -->
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing completed
## Screenshots
<!-- If applicable -->
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No new warningsReview Checklist
Functionality:
- Does the code do what it's supposed to?
- Are edge cases handled?
- Is error handling appropriate?
Code Quality:
- Is the code readable and maintainable?
- Are there any code smells?
- Is there unnecessary duplication?
Performance:
- Are there N+1 queries?
- Is caching used appropriately?
- Are there memory leaks?
Security:
- Is user input validated?
- Are there injection vulnerabilities?
- Is sensitive data protected?
Deployment Strategies
Blue-Green Deployment
Load Balancer
│
┌────────────┴────────────┐
│ │
┌────┴────┐ ┌─────┴────┐
│ Blue │ │ Green │
│ (Live) │ │ (Idle) │
└─────────┘ └──────────┘
1. Deploy new version to Green
2. Run smoke tests on Green
3. Switch traffic to Green
4. Blue becomes idle (rollback target)Canary Deployment
Load Balancer
│
┌────────────┴────────────┐
│ │
│ 95% 5% │
▼ ▼
┌─────────┐ ┌──────────┐
│ Stable │ │ Canary │
│ v1.0.0 │ │ v1.1.0 │
└─────────┘ └──────────┘
1. Deploy canary with small traffic %
2. Monitor error rates, latency
3. Gradually increase traffic
4. Full rollout or rollbackFeature Flags
// Feature flag service
const flags = {
newCheckoutFlow: {
enabled: true,
rolloutPercentage: 25,
allowedUsers: ["beta-testers"],
},
};
function isFeatureEnabled(flag: string, userId: string): boolean {
const config = flags[flag];
if (!config?.enabled) return false;
// Check allowed users
if (config.allowedUsers?.includes(userId)) return true;
// Check rollout percentage
const hash = hashUserId(userId);
return hash < config.rolloutPercentage;
}
// Usage
if (isFeatureEnabled("newCheckoutFlow", user.id)) {
return <NewCheckout />;
}
return <LegacyCheckout />;Monitoring and Observability
Structured Logging
import pino from "pino";
const logger = pino({
level: process.env.LOG_LEVEL || "info",
formatters: {
level: (label) => ({ level: label }),
},
});
// Request logging middleware
app.use((req, res, next) => {
const start = Date.now();
const requestId = req.headers["x-request-id"] || crypto.randomUUID();
res.on("finish", () => {
logger.info({
type: "request",
requestId,
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: Date.now() - start,
userAgent: req.headers["user-agent"],
});
});
next();
});
// Application logging
logger.info({ userId: user.id, action: "login" }, "User logged in");
logger.error({ err, orderId }, "Failed to process order");Metrics Collection
import { Counter, Histogram } from "prom-client";
const httpRequestsTotal = new Counter({
name: "http_requests_total",
help: "Total HTTP requests",
labelNames: ["method", "path", "status"],
});
const httpRequestDuration = new Histogram({
name: "http_request_duration_seconds",
help: "HTTP request duration",
labelNames: ["method", "path"],
buckets: [0.1, 0.3, 0.5, 1, 3, 5, 10],
});
// Middleware
app.use((req, res, next) => {
const end = httpRequestDuration.startTimer({
method: req.method,
path: req.route?.path || req.path,
});
res.on("finish", () => {
httpRequestsTotal.inc({
method: req.method,
path: req.route?.path || req.path,
status: res.statusCode,
});
end();
});
next();
});Health Checks
app.get("/health", async (req, res) => {
const checks = {
database: await checkDatabase(),
redis: await checkRedis(),
memory: checkMemory(),
};
const healthy = Object.values(checks).every((c) => c.status === "healthy");
res.status(healthy ? 200 : 503).json({
status: healthy ? "healthy" : "unhealthy",
checks,
timestamp: new Date().toISOString(),
});
});
async function checkDatabase() {
try {
await db.$queryRaw`SELECT 1`;
return { status: "healthy" };
} catch (error) {
return { status: "unhealthy", error: error.message };
}
}
function checkMemory() {
const used = process.memoryUsage();
const heapUsedMB = Math.round(used.heapUsed / 1024 / 1024);
const heapTotalMB = Math.round(used.heapTotal / 1024 / 1024);
return {
status: heapUsedMB < heapTotalMB * 0.9 ? "healthy" : "warning",
heapUsedMB,
heapTotalMB,
};
}Quick Reference
Daily Workflow
# 1. Start work
git checkout main && git pull
git checkout -b feature/my-feature
# 2. Develop with hot reload
docker-compose up -d
npm run dev
# 3. Test changes
npm run test
npm run lint
# 4. Commit
git add -A
git commit -m "feat(scope): description"
# 5. Push and create PR
git push -u origin feature/my-feature
gh pr createRelease Workflow
# 1. Ensure main is stable
git checkout main
npm run test:all
# 2. Create release
npm version minor # or major/patch
git push --follow-tags
# 3. Verify deployment
# CI/CD deploys automatically
# Monitor dashboards Fullstack Tech Stack Guide
Technology selection guide with trade-offs, use cases, and integration patterns for modern fullstack development.
Table of Contents
- Frontend Frameworks
- Backend Frameworks
- Databases
- ORMs and Query Builders
- Authentication Solutions
- Deployment Platforms
- Stack Recommendations
Frontend Frameworks
Next.js
Best for: Production React apps, SEO-critical sites, full-stack applications
| Pros | Cons |
|---|---|
| Server components, streaming | Learning curve for advanced features |
| Built-in routing, API routes | Vercel lock-in concerns |
| Excellent DX and performance | Bundle size can grow |
| Strong TypeScript support | Complex mental model (client/server) |
When to choose:
- Need SSR/SSG for SEO
- Building a product that may scale
- Want full-stack in one framework
- Team familiar with React
// App Router pattern
// app/users/page.tsx
async function UsersPage() {
const users = await db.user.findMany(); // Server component
return <UserList users={users} />;
}
// app/users/[id]/page.tsx
export async function generateStaticParams() {
const users = await db.user.findMany();
return users.map((user) => ({ id: user.id }));
}React + Vite
Best for: SPAs, dashboards, internal tools
| Pros | Cons |
|---|---|
| Fast development with HMR | No SSR out of the box |
| Simple mental model | Manual routing setup |
| Flexible architecture | No built-in API routes |
| Smaller bundle potential | Need separate backend |
When to choose:
- Building internal dashboards
- SEO not important
- Need maximum flexibility
- Prefer decoupled frontend/backend
Vue 3
Best for: Teams transitioning from jQuery, progressive enhancement
| Pros | Cons |
|---|---|
| Gentle learning curve | Smaller ecosystem than React |
| Excellent documentation | Fewer enterprise adoptions |
| Single-file components | Composition API learning curve |
| Good TypeScript support | Two paradigms (Options/Composition) |
When to choose:
- Team new to modern frameworks
- Progressive enhancement needed
- Prefer official solutions (Pinia, Vue Router)
Comparison Matrix
| Feature | Next.js | React+Vite | Vue 3 | Svelte |
|---|---|---|---|---|
| SSR | Built-in | Manual | Nuxt | SvelteKit |
| Bundle size | Medium | Small | Small | Smallest |
| Learning curve | Medium | Low | Low | Low |
| Enterprise adoption | High | High | Medium | Low |
| Job market | Large | Large | Medium | Small |
Backend Frameworks
Node.js Ecosystem
Express.js
import express from "express";
import { userRouter } from "./routes/users";
const app = express();
app.use(express.json());
app.use("/api/users", userRouter);
app.listen(3000);| Pros | Cons |
|---|---|
| Minimal, flexible | No structure opinions |
| Huge middleware ecosystem | Callback-based (legacy) |
| Well understood | Manual TypeScript setup |
Fastify
import Fastify from "fastify";
const app = Fastify({ logger: true });
app.get("/users/:id", {
schema: {
params: { type: "object", properties: { id: { type: "string" } } },
response: { 200: UserSchema },
},
handler: async (request) => {
return db.user.findUnique({ where: { id: request.params.id } });
},
});| Pros | Cons |
|---|---|
| High performance | Smaller ecosystem |
| Built-in validation | Different plugin model |
| TypeScript-first | Less community content |
NestJS
@Controller("users")
export class UsersController {
constructor(private usersService: UsersService) {}
@Get(":id")
findOne(@Param("id") id: string) {
return this.usersService.findOne(id);
}
@Post()
@UseGuards(AuthGuard)
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
}| Pros | Cons |
|---|---|
| Strong architecture | Steep learning curve |
| Full-featured (GraphQL, WebSockets) | Heavy for small projects |
| Enterprise-ready | Decorator complexity |
Python Ecosystem
FastAPI
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
app = FastAPI()
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, db: Session = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404)
return user| Pros | Cons |
|---|---|
| Auto-generated docs | Python GIL limitations |
| Type hints → validation | Async ecosystem maturing |
| High performance | Smaller than Django ecosystem |
Django
| Pros | Cons |
|---|---|
| Batteries included | Monolithic |
| Admin panel | ORM limitations |
| Mature ecosystem | Async support newer |
Framework Selection Guide
| Use Case | Recommendation |
|---|---|
| API-first startup | FastAPI or Fastify |
| Enterprise backend | NestJS or Django |
| Microservices | Fastify or Go |
| Rapid prototype | Express or Django |
| Full-stack TypeScript | Next.js API routes |
Databases
PostgreSQL
Best for: Most applications, relational data, ACID compliance
-- JSON support
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
profile JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Full-text search
CREATE INDEX users_search_idx ON users
USING GIN (to_tsvector('english', email || ' ' || profile->>'name'));
SELECT * FROM users
WHERE to_tsvector('english', email || ' ' || profile->>'name')
@@ to_tsquery('john');| Feature | Rating |
|---|---|
| ACID compliance | Excellent |
| JSON support | Excellent |
| Full-text search | Good |
| Horizontal scaling | Requires setup |
| Managed options | Many (RDS, Supabase, Neon) |
MongoDB
Best for: Document-heavy apps, flexible schemas, rapid prototyping
// Flexible schema
const userSchema = new Schema({
email: { type: String, required: true, unique: true },
profile: {
name: String,
preferences: Schema.Types.Mixed, // Any structure
},
orders: [{ type: Schema.Types.ObjectId, ref: "Order" }],
});| Feature | Rating |
|---|---|
| Schema flexibility | Excellent |
| Horizontal scaling | Excellent |
| Transactions | Good (4.0+) |
| Joins | Limited |
| Managed options | Atlas |
Redis
Best for: Caching, sessions, real-time features, queues
// Session storage
await redis.set(`session:${sessionId}`, JSON.stringify(user), "EX", 3600);
// Rate limiting
const requests = await redis.incr(`rate:${ip}`);
if (requests === 1) await redis.expire(`rate:${ip}`, 60);
if (requests > 100) throw new TooManyRequestsError();
// Pub/Sub
redis.publish("notifications", JSON.stringify({ userId, message }));Database Selection Matrix
| Requirement | PostgreSQL | MongoDB | MySQL |
|---|---|---|---|
| Complex queries | Best | Limited | Good |
| Schema flexibility | Good (JSONB) | Best | Limited |
| Transactions | Best | Good | Good |
| Horizontal scale | Manual | Built-in | Manual |
| Cloud managed | Many | Atlas | Many |
ORMs and Query Builders
Prisma
Best for: TypeScript projects, schema-first development
// schema.prisma
model User {
id String @id @default(cuid())
email String @unique
posts Post[]
profile Profile?
createdAt DateTime @default(now())
}
// Usage - fully typed
const user = await prisma.user.findUnique({
where: { email: "[email protected]" },
include: { posts: true, profile: true },
});
// user.posts is Post[] - TypeScript knows| Pros | Cons |
|---|---|
| Excellent TypeScript | Generated client size |
| Schema migrations | Limited raw SQL support |
| Visual studio | Some edge case limitations |
Drizzle
Best for: SQL-first TypeScript, performance-critical apps
// Schema definition
const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
email: varchar("email", { length: 255 }).notNull().unique(),
createdAt: timestamp("created_at").defaultNow(),
});
// Query - SQL-like syntax
const result = await db
.select()
.from(users)
.where(eq(users.email, "[email protected]"))
.leftJoin(posts, eq(posts.userId, users.id));| Pros | Cons |
|---|---|
| Lightweight | Newer, smaller community |
| SQL-like syntax | Fewer integrations |
| Fast runtime | Manual migrations |
SQLAlchemy (Python)
# Model definition
class User(Base):
__tablename__ = "users"
id = Column(UUID, primary_key=True, default=uuid4)
email = Column(String(255), unique=True, nullable=False)
posts = relationship("Post", back_populates="author")
# Query
users = session.query(User)\
.filter(User.email.like("%@example.com"))\
.options(joinedload(User.posts))\
.all()Authentication Solutions
Auth.js (NextAuth)
Best for: Next.js apps, social logins
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Credentials from "next-auth/providers/credentials";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GitHub,
Credentials({
credentials: { email: {}, password: {} },
authorize: async (credentials) => {
const user = await verifyCredentials(credentials);
return user;
},
}),
],
callbacks: {
jwt({ token, user }) {
if (user) token.role = user.role;
return token;
},
},
});| Pros | Cons |
|---|---|
| Many providers | Next.js focused |
| Session management | Complex customization |
| Database adapters | Breaking changes between versions |
Clerk
Best for: Rapid development, hosted solution
// Middleware
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
// Usage
import { auth } from "@clerk/nextjs/server";
export async function GET() {
const { userId } = await auth();
if (!userId) return new Response("Unauthorized", { status: 401 });
// ...
}| Pros | Cons |
|---|---|
| Beautiful UI components | Vendor lock-in |
| Managed infrastructure | Cost at scale |
| Multi-factor auth | Data residency concerns |
Custom JWT
Best for: Full control, microservices
// Token generation
function generateTokens(user: User) {
const accessToken = jwt.sign(
{ sub: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: "15m" }
);
const refreshToken = jwt.sign(
{ sub: user.id, version: user.tokenVersion },
process.env.REFRESH_SECRET,
{ expiresIn: "7d" }
);
return { accessToken, refreshToken };
}
// Middleware
function authenticate(req, res, next) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "No token" });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: "Invalid token" });
}
}Deployment Platforms
Vercel
Best for: Next.js, frontend-focused teams
| Pros | Cons |
|---|---|
| Zero-config Next.js | Expensive at scale |
| Edge functions | Vendor lock-in |
| Preview deployments | Limited backend options |
| Global CDN | Cold starts |
Railway
Best for: Full-stack apps, databases included
| Pros | Cons |
|---|---|
| Simple deployment | Smaller community |
| Built-in databases | Limited regions |
| Good pricing | Fewer integrations |
AWS (ECS/Lambda)
Best for: Enterprise, complex requirements
| Pros | Cons |
|---|---|
| Full control | Complex setup |
| Cost-effective at scale | Steep learning curve |
| Any technology | Requires DevOps knowledge |
Deployment Selection
| Requirement | Platform |
|---|---|
| Next.js simplicity | Vercel |
| Full-stack + DB | Railway, Render |
| Enterprise scale | AWS, GCP |
| Container control | Fly.io, Railway |
| Budget startup | Railway, Render |
Stack Recommendations
Startup MVP
Frontend: Next.js 14 (App Router)
Backend: Next.js API Routes
Database: PostgreSQL (Neon/Supabase)
Auth: Auth.js or Clerk
Deploy: Vercel
Cache: Vercel KV or Upstash RedisWhy: Fastest time to market, single deployment, good scaling path.
SaaS Product
Frontend: Next.js 14
Backend: Separate API (FastAPI or NestJS)
Database: PostgreSQL (RDS)
Auth: Custom JWT + Auth.js
Deploy: Vercel (frontend) + AWS ECS (backend)
Cache: Redis (ElastiCache)
Queue: SQS or BullMQWhy: Separation allows independent scaling, team specialization.
Enterprise Application
Frontend: Next.js or React + Vite
Backend: NestJS or Go
Database: PostgreSQL (Aurora)
Auth: Keycloak or Auth0
Deploy: Kubernetes (EKS/GKE)
Cache: Redis Cluster
Queue: Kafka or RabbitMQ
Observability: Datadog or Grafana StackWhy: Maximum control, compliance requirements, team expertise.
Internal Tool
Frontend: React + Vite + Tailwind
Backend: Express or FastAPI
Database: PostgreSQL or SQLite
Auth: OIDC with corporate IdP
Deploy: Docker on internal infrastructureWhy: Simple, low maintenance, integrates with existing systems.
Quick Decision Guide
| Question | If Yes → | If No → |
|---|---|---|
| Need SEO? | Next.js SSR | React SPA |
| Complex backend? | Separate API | Next.js routes |
| Team knows Python? | FastAPI | Node.js |
| Need real-time? | Add WebSockets | REST is fine |
| Enterprise compliance? | Self-hosted | Managed services |
| Budget constrained? | Railway/Render | Vercel/AWS |
| Schema changes often? | MongoDB | PostgreSQL |
#!/usr/bin/env python3
"""
Code Quality Analyzer
Analyzes fullstack codebases for quality issues including:
- Code complexity metrics (cyclomatic complexity, cognitive complexity)
- Security vulnerabilities (hardcoded secrets, injection patterns)
- Dependency health (outdated packages, known vulnerabilities)
- Test coverage estimation
- Documentation quality
Usage:
python code_quality_analyzer.py /path/to/project
python code_quality_analyzer.py . --json
python code_quality_analyzer.py /path/to/project --output report.json
"""
import argparse
import json
import os
import re
import sys
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Optional, Tuple
# File extensions to analyze
FRONTEND_EXTENSIONS = {".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"}
BACKEND_EXTENSIONS = {".py", ".go", ".java", ".rb", ".php", ".cs"}
CONFIG_EXTENSIONS = {".json", ".yaml", ".yml", ".toml", ".env"}
ALL_CODE_EXTENSIONS = FRONTEND_EXTENSIONS | BACKEND_EXTENSIONS
# Skip patterns
SKIP_DIRS = {"node_modules", "vendor", ".git", "__pycache__", "dist", "build",
".next", ".venv", "venv", "env", "coverage", ".pytest_cache"}
# Security patterns to detect
SECURITY_PATTERNS = {
"hardcoded_secret": {
"pattern": r"(?:password|secret|api_key|apikey|token|auth)[\s]*[=:][\s]*['\"][^'\"]{8,}['\"]",
"severity": "critical",
"message": "Potential hardcoded secret detected"
},
"sql_injection": {
"pattern": r"(?:execute|query|raw)\s*\(\s*[f'\"].*\{.*\}|%s|%d|\$\d",
"severity": "high",
"message": "Potential SQL injection vulnerability"
},
"xss_vulnerable": {
"pattern": r"innerHTML\s*=|v-html",
"severity": "medium",
"message": "Potential XSS vulnerability - unescaped HTML rendering"
},
"unsafe_react_html": {
"pattern": r"__html",
"severity": "medium",
"message": "React unsafe HTML pattern detected - ensure content is sanitized"
},
"insecure_protocol": {
"pattern": r"http://(?!localhost|127\.0\.0\.1)",
"severity": "medium",
"message": "Insecure HTTP protocol used"
},
"debug_code": {
"pattern": r"console\.log|print\(|debugger|DEBUG\s*=\s*True",
"severity": "low",
"message": "Debug code should be removed in production"
},
"todo_fixme": {
"pattern": r"(?:TODO|FIXME|HACK|XXX):",
"severity": "info",
"message": "Unresolved TODO/FIXME comment"
}
}
# Code smell patterns
CODE_SMELL_PATTERNS = {
"long_function": {
"description": "Function exceeds recommended length",
"threshold": 50
},
"deep_nesting": {
"description": "Excessive nesting depth",
"threshold": 4
},
"large_file": {
"description": "File exceeds recommended size",
"threshold": 500
},
"magic_number": {
"pattern": r"(?<![a-zA-Z_])\b(?:[2-9]\d{2,}|\d{4,})\b(?![a-zA-Z_])",
"description": "Magic number should be named constant"
}
}
# Dependency vulnerability patterns (simplified - in production use a CVE database)
KNOWN_VULNERABLE_DEPS = {
"lodash": {"vulnerable_below": "4.17.21", "cve": "CVE-2021-23337"},
"axios": {"vulnerable_below": "0.21.2", "cve": "CVE-2021-3749"},
"minimist": {"vulnerable_below": "1.2.6", "cve": "CVE-2021-44906"},
"jsonwebtoken": {"vulnerable_below": "9.0.0", "cve": "CVE-2022-23529"},
}
def should_skip(path: Path) -> bool:
"""Check if path should be skipped."""
return any(skip in path.parts for skip in SKIP_DIRS)
def count_lines(filepath: Path) -> Tuple[int, int, int]:
"""Count total lines, code lines, and comment lines."""
try:
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
lines = f.readlines()
except Exception:
return 0, 0, 0
total = len(lines)
code = 0
comments = 0
in_block_comment = False
for line in lines:
stripped = line.strip()
if not stripped:
continue
# Block comments
if "/*" in stripped:
in_block_comment = True
if in_block_comment:
comments += 1
if "*/" in stripped:
in_block_comment = False
continue
# Line comments
if stripped.startswith(("//", "#", "--", "'")):
comments += 1
else:
code += 1
return total, code, comments
def calculate_complexity(content: str, language: str) -> Dict:
"""Calculate cyclomatic complexity estimate."""
# Count decision points
decision_patterns = [
r"\bif\b", r"\belse\b", r"\belif\b", r"\bfor\b", r"\bwhile\b",
r"\bcase\b", r"\bcatch\b", r"\b\?\b", r"\b&&\b", r"\b\|\|\b",
r"\band\b", r"\bor\b"
]
complexity = 1 # Base complexity
for pattern in decision_patterns:
complexity += len(re.findall(pattern, content, re.IGNORECASE))
# Count nesting depth
max_depth = 0
current_depth = 0
for char in content:
if char == "{":
current_depth += 1
max_depth = max(max_depth, current_depth)
elif char == "}":
current_depth = max(0, current_depth - 1)
return {
"cyclomatic": complexity,
"max_nesting": max_depth,
"rating": "low" if complexity < 10 else "medium" if complexity < 20 else "high"
}
def analyze_security(filepath: Path, content: str) -> List[Dict]:
"""Scan for security issues."""
issues = []
lines = content.split("\n")
for pattern_name, pattern_info in SECURITY_PATTERNS.items():
regex = re.compile(pattern_info["pattern"], re.IGNORECASE)
for line_num, line in enumerate(lines, 1):
if regex.search(line):
issues.append({
"file": str(filepath),
"line": line_num,
"type": pattern_name,
"severity": pattern_info["severity"],
"message": pattern_info["message"]
})
return issues
def analyze_dependencies(project_path: Path) -> Dict:
"""Analyze project dependencies for issues."""
findings = {
"package_managers": [],
"total_deps": 0,
"outdated": [],
"vulnerable": [],
"recommendations": []
}
# Check package.json
package_json = project_path / "package.json"
if package_json.exists():
findings["package_managers"].append("npm")
try:
with open(package_json) as f:
pkg = json.load(f)
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
findings["total_deps"] += len(deps)
for dep, version in deps.items():
# Check against known vulnerabilities
if dep in KNOWN_VULNERABLE_DEPS:
vuln = KNOWN_VULNERABLE_DEPS[dep]
# Simplified version check
clean_version = re.sub(r"[^\d.]", "", version)
if clean_version and clean_version < vuln["vulnerable_below"]:
findings["vulnerable"].append({
"package": dep,
"current": version,
"fix_version": vuln["vulnerable_below"],
"cve": vuln["cve"]
})
except Exception:
pass
# Check requirements.txt
requirements = project_path / "requirements.txt"
if requirements.exists():
findings["package_managers"].append("pip")
try:
with open(requirements) as f:
lines = [l.strip() for l in f if l.strip() and not l.startswith("#")]
findings["total_deps"] += len(lines)
except Exception:
pass
# Check go.mod
go_mod = project_path / "go.mod"
if go_mod.exists():
findings["package_managers"].append("go")
return findings
def analyze_test_coverage(project_path: Path) -> Dict:
"""Estimate test coverage based on file analysis."""
test_files = []
source_files = []
for filepath in project_path.rglob("*"):
if should_skip(filepath) or not filepath.is_file():
continue
if filepath.suffix in ALL_CODE_EXTENSIONS:
name = filepath.stem.lower()
if "test" in name or "spec" in name or "_test" in name:
test_files.append(filepath)
elif not name.startswith("_"):
source_files.append(filepath)
source_count = len(source_files)
test_count = len(test_files)
# Estimate coverage ratio
if source_count == 0:
ratio = 0
else:
ratio = min(100, int((test_count / source_count) * 100))
return {
"source_files": source_count,
"test_files": test_count,
"estimated_coverage": ratio,
"rating": "good" if ratio >= 70 else "adequate" if ratio >= 40 else "poor",
"recommendation": None if ratio >= 70 else f"Consider adding more tests ({70 - ratio}% gap to target)"
}
def analyze_documentation(project_path: Path) -> Dict:
"""Analyze documentation quality."""
docs = {
"has_readme": False,
"has_contributing": False,
"has_license": False,
"has_changelog": False,
"api_docs": [],
"score": 0
}
readme_patterns = ["README.md", "README.rst", "README.txt", "readme.md"]
for pattern in readme_patterns:
if (project_path / pattern).exists():
docs["has_readme"] = True
docs["score"] += 30
break
if (project_path / "CONTRIBUTING.md").exists():
docs["has_contributing"] = True
docs["score"] += 15
license_patterns = ["LICENSE", "LICENSE.md", "LICENSE.txt"]
for pattern in license_patterns:
if (project_path / pattern).exists():
docs["has_license"] = True
docs["score"] += 15
break
changelog_patterns = ["CHANGELOG.md", "HISTORY.md", "CHANGES.md"]
for pattern in changelog_patterns:
if (project_path / pattern).exists():
docs["has_changelog"] = True
docs["score"] += 10
break
# Check for API docs
api_doc_dirs = ["docs", "documentation", "api-docs"]
for doc_dir in api_doc_dirs:
doc_path = project_path / doc_dir
if doc_path.is_dir():
docs["api_docs"].append(str(doc_path))
docs["score"] += 30
break
return docs
def analyze_project(project_path: Path) -> Dict:
"""Perform full project analysis."""
results = {
"summary": {
"files_analyzed": 0,
"total_lines": 0,
"code_lines": 0,
"comment_lines": 0
},
"languages": defaultdict(lambda: {"files": 0, "lines": 0}),
"security": {
"critical": [],
"high": [],
"medium": [],
"low": [],
"info": []
},
"complexity": {
"high_complexity_files": [],
"average_complexity": 0
},
"code_smells": [],
"dependencies": {},
"tests": {},
"documentation": {},
"overall_score": 100
}
complexity_scores = []
security_issues = []
# Analyze source files
for filepath in project_path.rglob("*"):
if should_skip(filepath) or not filepath.is_file():
continue
if filepath.suffix not in ALL_CODE_EXTENSIONS:
continue
results["summary"]["files_analyzed"] += 1
# Count lines
total, code, comments = count_lines(filepath)
results["summary"]["total_lines"] += total
results["summary"]["code_lines"] += code
results["summary"]["comment_lines"] += comments
# Track by language
lang = "typescript" if filepath.suffix in {".ts", ".tsx"} else \
"javascript" if filepath.suffix in {".js", ".jsx"} else \
"python" if filepath.suffix == ".py" else \
"go" if filepath.suffix == ".go" else "other"
results["languages"][lang]["files"] += 1
results["languages"][lang]["lines"] += code
# Read file content
try:
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
except Exception:
continue
# Complexity analysis
complexity = calculate_complexity(content, lang)
complexity_scores.append(complexity["cyclomatic"])
if complexity["rating"] == "high":
results["complexity"]["high_complexity_files"].append({
"file": str(filepath.relative_to(project_path)),
"complexity": complexity["cyclomatic"],
"nesting": complexity["max_nesting"]
})
# Security analysis
issues = analyze_security(filepath.relative_to(project_path), content)
security_issues.extend(issues)
# Code smell: large file
if total > CODE_SMELL_PATTERNS["large_file"]["threshold"]:
results["code_smells"].append({
"file": str(filepath.relative_to(project_path)),
"type": "large_file",
"details": f"{total} lines (threshold: {CODE_SMELL_PATTERNS['large_file']['threshold']})"
})
# Categorize security issues
for issue in security_issues:
severity = issue["severity"]
results["security"][severity].append(issue)
# Calculate average complexity
if complexity_scores:
results["complexity"]["average_complexity"] = round(
sum(complexity_scores) / len(complexity_scores), 1
)
# Dependency analysis
results["dependencies"] = analyze_dependencies(project_path)
# Test coverage analysis
results["tests"] = analyze_test_coverage(project_path)
# Documentation analysis
results["documentation"] = analyze_documentation(project_path)
# Calculate overall score
score = 100
# Deduct for security issues
score -= len(results["security"]["critical"]) * 15
score -= len(results["security"]["high"]) * 10
score -= len(results["security"]["medium"]) * 5
score -= len(results["security"]["low"]) * 2
# Deduct for high complexity
score -= len(results["complexity"]["high_complexity_files"]) * 3
# Deduct for code smells
score -= len(results["code_smells"]) * 2
# Deduct for vulnerable dependencies
score -= len(results["dependencies"].get("vulnerable", [])) * 10
# Deduct for poor test coverage
if results["tests"].get("estimated_coverage", 0) < 50:
score -= 15
elif results["tests"].get("estimated_coverage", 0) < 70:
score -= 5
# Deduct for missing documentation
doc_score = results["documentation"].get("score", 0)
if doc_score < 50:
score -= 10
elif doc_score < 75:
score -= 5
results["overall_score"] = max(0, min(100, score))
results["grade"] = (
"A" if score >= 90 else
"B" if score >= 80 else
"C" if score >= 70 else
"D" if score >= 60 else "F"
)
# Generate recommendations
results["recommendations"] = generate_recommendations(results)
# Convert defaultdict to regular dict for JSON serialization
results["languages"] = dict(results["languages"])
return results
def generate_recommendations(analysis: Dict) -> List[Dict]:
"""Generate prioritized recommendations."""
recs = []
# Critical security issues
for issue in analysis["security"]["critical"][:3]:
recs.append({
"priority": "P0",
"category": "security",
"issue": issue["message"],
"file": issue["file"],
"action": f"Remove or secure sensitive data at line {issue['line']}"
})
# Vulnerable dependencies
for vuln in analysis["dependencies"].get("vulnerable", [])[:3]:
recs.append({
"priority": "P0",
"category": "security",
"issue": f"Vulnerable dependency: {vuln['package']} ({vuln['cve']})",
"action": f"Update to version {vuln['fix_version']} or later"
})
# High security issues
for issue in analysis["security"]["high"][:3]:
recs.append({
"priority": "P1",
"category": "security",
"issue": issue["message"],
"file": issue["file"],
"action": "Review and fix security vulnerability"
})
# Test coverage
tests = analysis.get("tests", {})
if tests.get("estimated_coverage", 0) < 50:
recs.append({
"priority": "P1",
"category": "quality",
"issue": f"Low test coverage: {tests.get('estimated_coverage', 0)}%",
"action": "Add unit tests to improve coverage to at least 70%"
})
# High complexity files
for cplx in analysis["complexity"]["high_complexity_files"][:2]:
recs.append({
"priority": "P2",
"category": "maintainability",
"issue": f"High complexity in {cplx['file']}",
"action": "Refactor to reduce cyclomatic complexity"
})
# Documentation
docs = analysis.get("documentation", {})
if not docs.get("has_readme"):
recs.append({
"priority": "P2",
"category": "documentation",
"issue": "Missing README.md",
"action": "Add README with project overview and setup instructions"
})
return recs[:10]
def print_report(analysis: Dict, verbose: bool = False) -> None:
"""Print human-readable report."""
print("=" * 60)
print("CODE QUALITY ANALYSIS REPORT")
print("=" * 60)
print()
# Summary
summary = analysis["summary"]
print(f"Overall Score: {analysis['overall_score']}/100 (Grade: {analysis['grade']})")
print(f"Files Analyzed: {summary['files_analyzed']}")
print(f"Total Lines: {summary['total_lines']:,}")
print(f"Code Lines: {summary['code_lines']:,}")
print(f"Comment Lines: {summary['comment_lines']:,}")
print()
# Languages
print("--- LANGUAGES ---")
for lang, stats in analysis["languages"].items():
print(f" {lang}: {stats['files']} files, {stats['lines']:,} lines")
print()
# Security
sec = analysis["security"]
total_sec = sum(len(sec[s]) for s in ["critical", "high", "medium", "low"])
print("--- SECURITY ---")
print(f" Critical: {len(sec['critical'])}")
print(f" High: {len(sec['high'])}")
print(f" Medium: {len(sec['medium'])}")
print(f" Low: {len(sec['low'])}")
if total_sec > 0 and verbose:
print(" Issues:")
for severity in ["critical", "high", "medium"]:
for issue in sec[severity][:3]:
print(f" [{severity.upper()}] {issue['file']}:{issue['line']} - {issue['message']}")
print()
# Complexity
cplx = analysis["complexity"]
print("--- COMPLEXITY ---")
print(f" Average Complexity: {cplx['average_complexity']}")
print(f" High Complexity Files: {len(cplx['high_complexity_files'])}")
print()
# Dependencies
deps = analysis["dependencies"]
print("--- DEPENDENCIES ---")
print(f" Package Managers: {', '.join(deps.get('package_managers', ['none']))}")
print(f" Total Dependencies: {deps.get('total_deps', 0)}")
print(f" Vulnerable: {len(deps.get('vulnerable', []))}")
print()
# Tests
tests = analysis["tests"]
print("--- TEST COVERAGE ---")
print(f" Source Files: {tests.get('source_files', 0)}")
print(f" Test Files: {tests.get('test_files', 0)}")
print(f" Estimated Coverage: {tests.get('estimated_coverage', 0)}% ({tests.get('rating', 'unknown')})")
print()
# Documentation
docs = analysis["documentation"]
print("--- DOCUMENTATION ---")
print(f" README: {'Yes' if docs.get('has_readme') else 'No'}")
print(f" LICENSE: {'Yes' if docs.get('has_license') else 'No'}")
print(f" CONTRIBUTING: {'Yes' if docs.get('has_contributing') else 'No'}")
print(f" CHANGELOG: {'Yes' if docs.get('has_changelog') else 'No'}")
print(f" Score: {docs.get('score', 0)}/100")
print()
# Recommendations
if analysis["recommendations"]:
print("--- RECOMMENDATIONS ---")
for i, rec in enumerate(analysis["recommendations"][:10], 1):
print(f"\n{i}. [{rec['priority']}] {rec['category'].upper()}")
print(f" Issue: {rec['issue']}")
print(f" Action: {rec['action']}")
print()
print("=" * 60)
def main():
parser = argparse.ArgumentParser(
description="Analyze fullstack codebase for quality issues",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s /path/to/project
%(prog)s . --verbose
%(prog)s /path/to/project --json --output report.json
"""
)
parser.add_argument(
"project_path",
nargs="?",
default=".",
help="Path to project directory (default: current directory)"
)
parser.add_argument(
"--json",
action="store_true",
help="Output in JSON format"
)
parser.add_argument(
"--output", "-o",
help="Write output to file"
)
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="Show detailed findings"
)
args = parser.parse_args()
project_path = Path(args.project_path).resolve()
if not project_path.exists():
print(f"Error: Path does not exist: {project_path}", file=sys.stderr)
sys.exit(1)
analysis = analyze_project(project_path)
if args.json:
output = json.dumps(analysis, indent=2)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Report written to {args.output}")
else:
print(output)
else:
print_report(analysis, args.verbose)
if args.output:
with open(args.output, "w") as f:
json.dump(analysis, f, indent=2)
print(f"\nDetailed JSON report written to {args.output}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Fullstack Project Scaffolder
Generates project structure and boilerplate for various fullstack architectures.
Supports Next.js, FastAPI+React, MERN, Django+React, and more.
Usage:
python project_scaffolder.py nextjs my-app
python project_scaffolder.py fastapi-react my-api --with-docker
python project_scaffolder.py mern my-project --with-auth
python project_scaffolder.py --list-templates
"""
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Optional
# Project templates with file structures
TEMPLATES = {
"nextjs": {
"name": "Next.js Full Stack",
"description": "Next.js 14+ with App Router, TypeScript, Tailwind CSS",
"structure": {
"src/app": ["layout.tsx", "page.tsx", "globals.css", "api/health/route.ts"],
"src/components/ui": ["Button.tsx", "Card.tsx", "Input.tsx"],
"src/components/layout": ["Header.tsx", "Footer.tsx"],
"src/lib": ["utils.ts", "db.ts"],
"src/types": ["index.ts"],
"public": [],
"": ["package.json", "tsconfig.json", "tailwind.config.ts", "next.config.js",
".env.example", ".gitignore", "README.md"]
}
},
"fastapi-react": {
"name": "FastAPI + React",
"description": "FastAPI backend with React frontend, PostgreSQL",
"structure": {
"backend/app": ["__init__.py", "main.py", "config.py", "database.py"],
"backend/app/api": ["__init__.py", "routes.py", "deps.py"],
"backend/app/models": ["__init__.py", "user.py"],
"backend/app/schemas": ["__init__.py", "user.py"],
"backend": ["requirements.txt", "alembic.ini", "Dockerfile"],
"frontend/src": ["App.tsx", "main.tsx", "index.css"],
"frontend/src/components": ["Layout.tsx"],
"frontend/src/hooks": ["useApi.ts"],
"frontend": ["package.json", "tsconfig.json", "vite.config.ts", "Dockerfile"],
"": ["docker-compose.yml", ".env.example", ".gitignore", "README.md"]
}
},
"mern": {
"name": "MERN Stack",
"description": "MongoDB, Express, React, Node.js with TypeScript",
"structure": {
"server/src": ["index.ts", "config.ts", "database.ts"],
"server/src/routes": ["index.ts", "users.ts"],
"server/src/models": ["User.ts"],
"server/src/middleware": ["auth.ts", "error.ts"],
"server": ["package.json", "tsconfig.json", "Dockerfile"],
"client/src": ["App.tsx", "main.tsx"],
"client/src/components": ["Layout.tsx"],
"client/src/services": ["api.ts"],
"client": ["package.json", "tsconfig.json", "vite.config.ts", "Dockerfile"],
"": ["docker-compose.yml", ".env.example", ".gitignore", "README.md"]
}
},
"django-react": {
"name": "Django + React",
"description": "Django REST Framework backend with React frontend",
"structure": {
"backend/config": ["__init__.py", "settings.py", "urls.py", "wsgi.py"],
"backend/apps/users": ["__init__.py", "models.py", "serializers.py", "views.py", "urls.py"],
"backend": ["manage.py", "requirements.txt", "Dockerfile"],
"frontend/src": ["App.tsx", "main.tsx"],
"frontend/src/components": ["Layout.tsx"],
"frontend": ["package.json", "tsconfig.json", "vite.config.ts", "Dockerfile"],
"": ["docker-compose.yml", ".env.example", ".gitignore", "README.md"]
}
}
}
def get_file_content(template: str, filepath: str, project_name: str) -> str:
"""Generate file content based on template and file type."""
filename = Path(filepath).name
contents = {
# Next.js files
"layout.tsx": f'''import type {{ Metadata }} from "next";
import "./globals.css";
export const metadata: Metadata = {{
title: "{project_name}",
description: "Generated by project scaffolder",
}};
export default function RootLayout({{
children,
}}: {{
children: React.ReactNode;
}}) {{
return (
<html lang="en">
<body>{{children}}</body>
</html>
);
}}
''',
"page.tsx": f'''export default function Home() {{
return (
<main className="min-h-screen p-8">
<h1 className="text-4xl font-bold">{project_name}</h1>
<p className="mt-4 text-gray-600">Welcome to your new project.</p>
</main>
);
}}
''',
"globals.css": '''@tailwind base;
@tailwind components;
@tailwind utilities;
''',
"route.ts": '''import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({
status: "healthy",
timestamp: new Date().toISOString(),
});
}
''',
"Button.tsx": '''import { ButtonHTMLAttributes, forwardRef } from "react";
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "outline";
size?: "sm" | "md" | "lg";
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className = "", variant = "primary", size = "md", ...props }, ref) => {
const base = "font-medium rounded-lg transition-colors";
const variants = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
outline: "border border-gray-300 hover:bg-gray-50",
};
const sizes = { sm: "px-3 py-1.5 text-sm", md: "px-4 py-2", lg: "px-6 py-3 text-lg" };
return <button ref={ref} className={`${base} ${variants[variant]} ${sizes[size]} ${className}`} {...props} />;
}
);
Button.displayName = "Button";
''',
"Card.tsx": '''interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
export function Card({ className = "", children, ...props }: CardProps) {
return (
<div className={`rounded-lg border bg-white p-6 shadow-sm ${className}`} {...props}>
{children}
</div>
);
}
''',
"Input.tsx": '''import { InputHTMLAttributes, forwardRef } from "react";
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
}
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ label, error, className = "", ...props }, ref) => (
<div className="space-y-1">
{label && <label className="text-sm font-medium text-gray-700">{label}</label>}
<input
ref={ref}
className={`w-full rounded-md border px-3 py-2 ${error ? "border-red-500" : "border-gray-300"} ${className}`}
{...props}
/>
{error && <p className="text-sm text-red-500">{error}</p>}
</div>
)
);
Input.displayName = "Input";
''',
"Header.tsx": f'''import Link from "next/link";
export function Header() {{
return (
<header className="border-b">
<nav className="mx-auto flex max-w-7xl items-center justify-between p-4">
<Link href="/" className="text-xl font-bold">{project_name}</Link>
<div className="flex gap-4">
<Link href="/about" className="hover:text-blue-600">About</Link>
</div>
</nav>
</header>
);
}}
''',
"Footer.tsx": '''export function Footer() {
return (
<footer className="border-t py-8 text-center text-sm text-gray-500">
<p>© {new Date().getFullYear()} All rights reserved.</p>
</footer>
);
}
''',
"utils.ts": '''export function cn(...classes: (string | undefined | false)[]): string {
return classes.filter(Boolean).join(" ");
}
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat("en-US", {
year: "numeric", month: "long", day: "numeric",
}).format(date);
}
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
''',
"db.ts": '''// Database connection (Prisma example)
// import { PrismaClient } from "@prisma/client";
// export const db = new PrismaClient();
export const db = {
// Placeholder - configure your database
};
''',
"index.ts": '''export interface User {
id: string;
email: string;
name: string | null;
createdAt: Date;
}
export interface ApiResponse<T> {
data: T;
error: string | null;
success: boolean;
}
''',
# FastAPI files
"main.py": f'''from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.routes import router
from app.config import settings
app = FastAPI(title="{project_name}", openapi_url="/api/openapi.json")
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(router, prefix="/api")
@app.get("/health")
async def health_check():
return {{"status": "healthy"}}
''',
"config.py": '''from pydantic_settings import BaseSettings
from typing import List
class Settings(BaseSettings):
DATABASE_URL: str = "postgresql://user:pass@localhost:5432/db"
ALLOWED_ORIGINS: List[str] = ["http://localhost:3000", "http://localhost:5173"]
SECRET_KEY: str = "change-me-in-production" # ⚠️ SCAFFOLDING PLACEHOLDER — replace before deployment
class Config:
env_file = ".env"
settings = Settings()
''',
"database.py": '''from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.config import settings
engine = create_engine(settings.DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
''',
"routes.py": '''from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.database import get_db
router = APIRouter()
@router.get("/users")
async def get_users(db: Session = Depends(get_db)):
return {"users": []}
@router.post("/users")
async def create_user(db: Session = Depends(get_db)):
return {"message": "User created"}
''',
"deps.py": '''from typing import Generator
from fastapi import Depends
from sqlalchemy.orm import Session
from app.database import get_db
def get_current_user():
# Implement authentication
pass
''',
"user.py": '''from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.sql import func
from app.database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, nullable=False)
name = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.now())
''',
"requirements.txt": '''fastapi>=0.104.0
uvicorn[standard]>=0.24.0
sqlalchemy>=2.0.0
pydantic>=2.0.0
pydantic-settings>=2.0.0
alembic>=1.12.0
psycopg2-binary>=2.9.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.0
''',
"alembic.ini": '''[alembic]
script_location = alembic
sqlalchemy.url = driver://user:pass@localhost/dbname
''',
# Express files
"index.ts": '''import express from "express";
import cors from "cors";
import helmet from "helmet";
import routes from "./routes";
const app = express();
const PORT = process.env.PORT || 8000;
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use("/api", routes);
app.get("/health", (_, res) => res.json({ status: "healthy" }));
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
''',
"config.ts": '''export const config = {
PORT: parseInt(process.env.PORT || "8000"),
MONGODB_URI: process.env.MONGODB_URI || "mongodb://localhost:27017/app",
JWT_SECRET: process.env.JWT_SECRET || "change-me",
};
''',
"database.ts": '''import mongoose from "mongoose";
import { config } from "./config";
export async function connectDatabase(): Promise<void> {
await mongoose.connect(config.MONGODB_URI);
console.log("Connected to MongoDB");
}
''',
"users.ts": '''import { Router } from "express";
const router = Router();
router.get("/", async (req, res) => {
res.json({ users: [] });
});
router.post("/", async (req, res) => {
res.status(201).json({ message: "User created" });
});
export default router;
''',
"User.ts": '''import mongoose, { Schema, Document } from "mongoose";
export interface IUser extends Document {
email: string;
name?: string;
createdAt: Date;
}
const userSchema = new Schema<IUser>({
email: { type: String, required: true, unique: true },
name: String,
}, { timestamps: true });
export const User = mongoose.model<IUser>("User", userSchema);
''',
"auth.ts": '''import { Request, Response, NextFunction } from "express";
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "Authentication required" });
// Verify token
next();
}
''',
"error.ts": '''import { Request, Response, NextFunction } from "express";
export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
console.error(err.stack);
res.status(500).json({ error: "Internal server error" });
}
''',
# React/Vite files
"App.tsx": f'''function App() {{
return (
<div className="min-h-screen bg-gray-50">
<main className="container mx-auto p-4">
<h1 className="text-3xl font-bold">{project_name}</h1>
<p className="mt-4 text-gray-600">Welcome to your new project.</p>
</main>
</div>
);
}}
export default App;
''',
"main.tsx": '''import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode><App /></React.StrictMode>
);
''',
"index.css": '''@tailwind base;
@tailwind components;
@tailwind utilities;
''',
"Layout.tsx": f'''import {{ ReactNode }} from "react";
export function Layout({{ children }}: {{ children: ReactNode }}) {{
return (
<div className="min-h-screen">
<header className="border-b p-4">
<a href="/" className="text-xl font-bold">{project_name}</a>
</header>
<main>{{children}}</main>
</div>
);
}}
''',
"useApi.ts": '''import { useState, useCallback } from "react";
export function useApi<T>(baseUrl = "/api") {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const request = useCallback(async (endpoint: string, options?: RequestInit) => {
setLoading(true);
setError(null);
try {
const res = await fetch(`${baseUrl}${endpoint}`, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
setData(json);
return json;
} catch (e) {
setError(e instanceof Error ? e.message : "Unknown error");
throw e;
} finally {
setLoading(false);
}
}, [baseUrl]);
return { data, loading, error, request };
}
''',
"api.ts": '''const API_BASE = import.meta.env.VITE_API_URL || "/api";
async function request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const res = await fetch(`${API_BASE}${endpoint}`, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
export const api = {
get: <T>(endpoint: string) => request<T>(endpoint),
post: <T>(endpoint: string, body: unknown) => request<T>(endpoint, { method: "POST", body: JSON.stringify(body) }),
put: <T>(endpoint: string, body: unknown) => request<T>(endpoint, { method: "PUT", body: JSON.stringify(body) }),
delete: <T>(endpoint: string) => request<T>(endpoint, { method: "DELETE" }),
};
''',
"vite.config.ts": '''import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
proxy: { "/api": { target: "http://localhost:8000", changeOrigin: true } },
},
});
''',
# Django files
"settings.py": f'''from pathlib import Path
import os
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "change-me")
DEBUG = os.environ.get("DEBUG", "True") == "True"
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"corsheaders",
"apps.users",
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
]
ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"
DATABASES = {{
"default": {{
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ.get("DB_NAME", "app"),
"USER": os.environ.get("DB_USER", "user"),
"PASSWORD": os.environ.get("DB_PASSWORD", "password"),
"HOST": os.environ.get("DB_HOST", "localhost"),
"PORT": "5432",
}}
}}
CORS_ALLOWED_ORIGINS = ["http://localhost:5173"]
''',
"urls.py": '''from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("apps.users.urls")),
]
''',
"wsgi.py": '''import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()
''',
"models.py": '''from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
email = models.EmailField(unique=True)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username"]
''',
"serializers.py": '''from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "email", "username", "date_joined"]
''',
"views.py": '''from rest_framework import viewsets
from .models import User
from .serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
''',
"manage.py": '''#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
''',
# Config files
"package.json": f'''{{"name": "{project_name}", "version": "0.1.0", "private": true,
"scripts": {{"dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint"}},
"dependencies": {{"next": "^14.0.0", "react": "^18.2.0", "react-dom": "^18.2.0"}},
"devDependencies": {{"@types/node": "^20.0.0", "@types/react": "^18.2.0", "typescript": "^5.0.0", "tailwindcss": "^3.3.0", "autoprefixer": "^10.4.0", "postcss": "^8.4.0"}}
}}''',
"tsconfig.json": '''{"compilerOptions": {"target": "ES2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "baseUrl": ".", "paths": {"@/*": ["./src/*"]}}, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["node_modules"]}''',
"tailwind.config.ts": '''import type { Config } from "tailwindcss";
const config: Config = { content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"], theme: { extend: {} }, plugins: [] };
export default config;
''',
"next.config.js": '''/** @type {import('next').NextConfig} */
module.exports = { reactStrictMode: true };
''',
".env.example": '''DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
SECRET_KEY="your-secret-here" # ⚠️ SCAFFOLDING PLACEHOLDER — replace before deployment
''',
".gitignore": '''node_modules/
.next/
dist/
build/
.env
.env.local
__pycache__/
*.pyc
.venv/
.DS_Store
''',
"docker-compose.yml": f'''version: "3.8"
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: {project_name}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
''',
"Dockerfile": '''FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
''',
"README.md": f'''# {project_name}
Generated by Fullstack Project Scaffolder.
## Getting Started
```bash
npm install
npm run dev
```
Open http://localhost:3000.
''',
"__init__.py": "",
}
# Handle special cases
if "routes" in filepath and filename == "index.ts":
return '''import { Router } from "express";
import usersRouter from "./users";
const router = Router();
router.use("/users", usersRouter);
export default router;
'''
if "schemas" in filepath and filename == "user.py":
return '''from pydantic import BaseModel, EmailStr
from datetime import datetime
from typing import Optional
class UserBase(BaseModel):
email: EmailStr
name: Optional[str] = None
class UserCreate(UserBase):
password: str
class UserResponse(UserBase):
id: int
created_at: datetime
class Config:
from_attributes = True
'''
if "users" in filepath and filename == "urls.py":
return '''from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import UserViewSet
router = DefaultRouter()
router.register("users", UserViewSet)
urlpatterns = [path("", include(router.urls))]
'''
return contents.get(filename, f"// {filename}\n")
def create_project(template_name: str, project_name: str, output_dir: Path) -> Dict:
"""Create project from template."""
if template_name not in TEMPLATES:
return {
"success": False,
"error": f"Unknown template: {template_name}",
"available": list(TEMPLATES.keys())
}
template = TEMPLATES[template_name]
project_dir = output_dir / project_name
if project_dir.exists():
return {"success": False, "error": f"Directory exists: {project_dir}"}
created_files = []
created_dirs = []
for dir_path, files in template["structure"].items():
if dir_path:
full_dir = project_dir / dir_path
else:
full_dir = project_dir
full_dir.mkdir(parents=True, exist_ok=True)
created_dirs.append(str(full_dir))
for filename in files:
filepath = full_dir / filename
filepath.parent.mkdir(parents=True, exist_ok=True)
content = get_file_content(template_name, str(dir_path / filename), project_name)
filepath.write_text(content)
created_files.append(str(filepath))
return {
"success": True,
"project_name": project_name,
"template": template_name,
"description": template["description"],
"location": str(project_dir),
"files_created": len(created_files),
"directories_created": len(created_dirs),
"next_steps": get_next_steps(template_name, project_name)
}
def get_next_steps(template: str, name: str) -> List[str]:
"""Get setup instructions for template."""
steps = {
"nextjs": [f"cd {name}", "npm install", "cp .env.example .env.local", "npm run dev"],
"fastapi-react": [
f"cd {name}",
"docker-compose up -d db",
"cd backend && pip install -r requirements.txt && uvicorn app.main:app --reload",
"cd frontend && npm install && npm run dev"
],
"mern": [
f"cd {name}",
"docker-compose up -d mongo",
"cd server && npm install && npm run dev",
"cd client && npm install && npm run dev"
],
"django-react": [
f"cd {name}",
"docker-compose up -d db",
"cd backend && pip install -r requirements.txt && python manage.py migrate && python manage.py runserver",
"cd frontend && npm install && npm run dev"
]
}
return steps.get(template, [f"cd {name}"])
def list_templates() -> Dict:
"""List available templates."""
return {
"templates": [
{"name": k, "display_name": v["name"], "description": v["description"]}
for k, v in TEMPLATES.items()
]
}
def print_result(result: Dict, as_json: bool = False) -> None:
"""Print result."""
if as_json:
print(json.dumps(result, indent=2))
return
if "templates" in result:
print("\nAvailable Templates:")
print("=" * 50)
for t in result["templates"]:
print(f"\n {t['name']}")
print(f" {t['description']}")
return
if not result.get("success"):
print(f"Error: {result.get('error')}")
return
print("\n" + "=" * 50)
print("Project Created Successfully")
print("=" * 50)
print(f"Project: {result['project_name']}")
print(f"Template: {result['template']}")
print(f"Location: {result['location']}")
print(f"Files: {result['files_created']}")
print("\nNext Steps:")
for i, step in enumerate(result["next_steps"], 1):
print(f" {i}. {step}")
print()
def main():
parser = argparse.ArgumentParser(
description="Generate fullstack project scaffolding",
epilog="Examples:\n %(prog)s nextjs my-app\n %(prog)s fastapi-react my-api\n %(prog)s --list-templates",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("template", nargs="?", help="Project template")
parser.add_argument("project_name", nargs="?", help="Project name")
parser.add_argument("--output", "-o", default=".", help="Output directory")
parser.add_argument("--list-templates", "-l", action="store_true", help="List templates")
parser.add_argument("--json", action="store_true", help="JSON output")
args = parser.parse_args()
if args.list_templates:
print_result(list_templates(), args.json)
return
if not args.template or not args.project_name:
parser.print_help()
sys.exit(1)
result = create_project(args.template, args.project_name, Path(args.output).resolve())
print_result(result, args.json)
if not result.get("success"):
sys.exit(1)
if __name__ == "__main__":
main()
Install this Skill
Skills give your AI agent a consistent, structured approach to this task — better output than a one-off prompt.
npx skills add alirezarezvani/claude-skills --skill engineering-team/senior-fullstack Community skill by @alirezarezvani. Need a walkthrough? See the install guide →
Works with
Prefer no terminal? Download the ZIP and place it manually.
Details
- Category
- Development
- License
- MIT
- Author
- @alirezarezvani
- Source
- GitHub →
- Source file
-
show path
engineering-team/senior-fullstack/SKILL.md
People who install this also use
Senior Frontend Engineer
React and Next.js component development, bundle optimization, performance tuning, and accessibility best practices from a senior engineer perspective.
@alirezarezvani
Senior Backend Engineer
REST and GraphQL API development, database schema optimization, authentication patterns, and backend architecture decisions from a senior engineer.
@alirezarezvani
SaaS Scaffolder
Generate production-ready SaaS boilerplate with Next.js, TypeScript, authentication, payments (Stripe), and Docker — skip months of setup work.
@alirezarezvani