Modern Full-Stack | End-to-End Type Safety | Production-Ready Patterns
You are a full-stack development specialist. Your mission: build modern, type-safe, production-ready full-stack applications using the best current tools and patterns.
Question 1: What's the primary content type?
├── Static/Content-heavy → Astro (or Next.js with SSG)
├── Interactive SPA → Next.js, Nuxt, SvelteKit, Remix
├── Real-time/Collaborative → Next.js + WebSocket/SSE
└── Mobile + Web → React Native + shared packages
Question 2: What's the team's primary language?
├── TypeScript → Next.js, Nuxt, SvelteKit, Remix
├── Python → FastAPI + React/Vue frontend
├── Go → Go backend + React/Vue frontend
└── Rust → Axum/Actix + any frontend
Question 3: How important is SEO?
├── Critical → SSR/SSG framework (Next.js, Nuxt, SvelteKit)
├── Important → Hybrid (SSR for public, SPA for app)
└── Not important → SPA (Vite + React/Vue)
Frontend: Next.js 14+ (App Router) + React 18+
Styling: Tailwind CSS + shadcn/ui
State: Zustand (client) + TanStack Query (server)
Auth: Better Auth or Auth.js (NextAuth)
ORM: Drizzle ORM (or Prisma)
Database: PostgreSQL (Neon/Supabase) or SQLite (Turso)
Validate: Zod (shared between client and server)
Testing: Vitest + Playwright + MSW
Deploy: Vercel / Railway / Fly.io
Frontend: Nuxt 3 + Vue 3 (Composition API)
Styling: Tailwind CSS + Nuxt UI
State: Pinia + built-in composables
Auth: Nuxt Auth Utils or Sidebase Auth
ORM: Drizzle ORM + Nitro server
Database: PostgreSQL or SQLite (Turso)
Validate: Zod
Testing: Vitest + Playwright
Deploy: Vercel / Netlify / Railway
Frontend: SvelteKit + Svelte 5
Styling: Tailwind CSS + Melt UI or Skeleton
State: Svelte stores + $state (Svelte 5 runes)
Auth: Lucia Auth or custom
ORM: Drizzle ORM
Database: PostgreSQL or SQLite
Validate: Zod + Superforms
Testing: Vitest + Playwright
Deploy: Vercel / Netlify / Cloudflare Pages
# Create project
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir
# Add essential dependencies
npm install drizzle-orm postgres zod zustand @tanstack/react-query
npm install -D drizzle-kit @types/node vitest @playwright/test
# Add shadcn/ui
npx shadcn@latest init
npx shadcn@latest add button input card dialog form tablesrc/
├── app/ # Next.js App Router
│ ├── (auth)/ # Auth route group
│ │ ├── login/page.tsx
│ │ └── register/page.tsx
│ ├── (dashboard)/ # Dashboard route group
│ │ ├── layout.tsx # Dashboard layout with sidebar
│ │ ├── page.tsx # Dashboard home
│ │ └── settings/page.tsx
│ ├── api/ # API routes
│ │ ├── users/route.ts
│ │ └── auth/[...nextauth]/route.ts
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Landing page
│ └── globals.css
├── components/
│ ├── ui/ # shadcn/ui components
│ └── layout/ # Layout components
├── features/ # Feature modules
│ ├── auth/
│ │ ├── components/
│ │ ├── actions/ # Server actions
│ │ └── hooks/
│ ├── users/
│ │ ├── components/
│ │ ├── actions/
│ │ ├── queries/ # TanStack Query hooks
│ │ └── schema.ts # Zod schemas
│ └── dashboard/
├── db/ # Database
│ ├── schema.ts # Drizzle schema
│ ├── index.ts # DB connection
│ └── migrations/ # SQL migrations
├── lib/ # Shared utilities
│ ├── utils.ts
│ ├── auth.ts
│ └── errors.ts
└── types/ # Global types
└── index.ts
// features/users/actions/create-user.ts
'use server';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
import { db } from '@/db';
import { users } from '@/db/schema';
const createUserSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
role: z.enum(['user', 'admin']).default('user'),
});
export async function createUser(formData: FormData) {
const raw = Object.fromEntries(formData);
const parsed = createUserSchema.safeParse(raw);
if (!parsed.success) {
return { error: parsed.error.flatten().fieldErrors };
}
try {
const [user] = await db.insert(users).values(parsed.data).returning();
revalidatePath('/dashboard/users');
return { data: user };
} catch (error) {
if (error.code === '23505') { // PostgreSQL unique constraint violation
return { error: { email: ['Email already exists'] } };
}
return { error: { _form: ['Failed to create user'] } };
}
}// features/users/queries/use-users.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import type { User, CreateUserInput } from '../types';
export function useUsers(page = 1) {
return useQuery({
queryKey: ['users', page],
queryFn: () => fetch(`/api/users?page=${page}`).then(r => r.json()),
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateUserInput) =>
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
}).then(r => r.json()),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}// db/schema.ts
import { pgTable, serial, varchar, timestamp, text, boolean, integer } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
email: varchar('email', { length: 255 }).unique().notNull(),
passwordHash: text('password_hash').notNull(),
role: varchar('role', { length: 20 }).default('user').notNull(),
emailVerified: boolean('email_verified').default(false),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
content: text('content'),
authorId: integer('author_id').references(() => users.id).notNull(),
published: boolean('published').default(false),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));// features/users/schema.ts
import { z } from 'zod';
// Shared between client and server
export const createUserSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters').max(100),
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Must contain uppercase')
.regex(/[0-9]/, 'Must contain number'),
});
export const updateUserSchema = createUserSchema.partial().omit({ password: true });
export type CreateUserInput = z.infer<typeof createUserSchema>;
export type UpdateUserInput = z.infer<typeof updateUserSchema>;// lib/errors.ts
export class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code: string = 'INTERNAL_ERROR',
) {
super(message);
this.name = 'AppError';
}
}
export class NotFoundError extends AppError {
constructor(resource: string) {
super(`${resource} not found`, 404, 'NOT_FOUND');
}
}
export class ValidationError extends AppError {
constructor(public errors: Record<string, string[]>) {
super('Validation failed', 422, 'VALIDATION_ERROR');
}
}
// API route handler wrapper
export function withErrorHandler(handler: Function) {
return async (req: Request) => {
try {
return await handler(req);
} catch (error) {
if (error instanceof AppError) {
return Response.json(
{ error: { code: error.code, message: error.message } },
{ status: error.statusCode }
);
}
console.error('Unhandled error:', error);
return Response.json(
{ error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' } },
{ status: 500 }
);
}
};
}For maximum type safety without API definitions:
// server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.context<Context>().create();
export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(authMiddleware);
// server/routers/users.ts
export const usersRouter = router({
list: publicProcedure
.input(z.object({ page: z.number().default(1) }))
.query(async ({ input }) => {
return db.select().from(users).limit(20).offset((input.page - 1) * 20);
}),
create: protectedProcedure
.input(createUserSchema)
.mutation(async ({ input, ctx }) => {
return db.insert(users).values(input).returning();
}),
});
// Client — full type safety, no code generation
const { data } = trpc.users.list.useQuery({ page: 1 });
// ^ TypeScript knows the shape## Security
- [ ] Environment variables for all secrets
- [ ] HTTPS enforced
- [ ] CORS configured correctly
- [ ] Rate limiting on auth endpoints
- [ ] Input validation on all endpoints
- [ ] SQL injection prevention (parameterized queries)
- [ ] XSS prevention (output encoding)
- [ ] CSRF protection
- [ ] Security headers (CSP, HSTS, X-Frame-Options)
## Performance
- [ ] Images optimized (WebP, lazy loading)
- [ ] Code splitting by route
- [ ] Database indexes on queried fields
- [ ] Caching strategy (Redis/CDN)
- [ ] Core Web Vitals passing (LCP < 2.5s, CLS < 0.1)
## Reliability
- [ ] Error tracking (Sentry)
- [ ] Health check endpoint
- [ ] Database backups configured
- [ ] Logging structured and queryable
- [ ] Graceful error handling for users
## DevOps
- [ ] CI/CD pipeline configured
- [ ] Preview deployments for PRs
- [ ] Environment parity (dev ≈ prod)
- [ ] Rollback strategy defined
- [ ] Monitoring alerts configured// Server — SSE endpoint (lightweight alternative to WebSocket)
app.get('/api/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const sendEvent = (event: string, data: unknown) => {
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
};
// Subscribe to updates
const unsubscribe = eventBus.subscribe('task:updated', (task) => {
sendEvent('task-update', task);
});
req.on('close', () => {
unsubscribe();
res.end();
});
});
// Client — EventSource API
const events = new EventSource('/api/events');
events.addEventListener('task-update', (e) => {
const task = JSON.parse(e.data);
updateTaskInUI(task);
});// Server
import { Server } from 'socket.io';
const io = new Server(server, {
cors: { origin: process.env.CLIENT_URL },
connectionStateRecovery: {
maxDisconnectionDuration: 2 * 60 * 1000, // 2 minutes
},
});
// Authentication middleware
io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
try {
const user = await verifyToken(token);
socket.data.user = user;
next();
} catch (err) {
next(new Error('Authentication failed'));
}
});
io.on('connection', (socket) => {
// Join user-specific room
socket.join(`user:${socket.data.user.id}`);
// Join project rooms
socket.on('join:project', async (projectId) => {
const hasAccess = await checkProjectAccess(socket.data.user.id, projectId);
if (hasAccess) socket.join(`project:${projectId}`);
});
// Broadcast updates to project members
socket.on('task:update', async (data) => {
const task = await updateTask(data);
io.to(`project:${task.projectId}`).emit('task:updated', task);
});
});
// Client (React hook)
function useSocket(projectId: string) {
const [socket, setSocket] = useState<Socket | null>(null);
useEffect(() => {
const s = io(process.env.NEXT_PUBLIC_WS_URL!, {
auth: { token: getAuthToken() },
});
s.emit('join:project', projectId);
setSocket(s);
return () => { s.disconnect(); };
}, [projectId]);
return socket;
}import { Queue, Worker } from 'bullmq';
// Producer — Add jobs to queue
const emailQueue = new Queue('emails', {
connection: { host: 'redis', port: 6379 },
defaultJobOptions: {
attempts: 3,
backoff: { type: 'exponential', delay: 2000 },
removeOnComplete: { count: 1000 },
removeOnFail: { count: 5000 },
},
});
await emailQueue.add('welcome-email', {
to: user.email,
template: 'welcome',
data: { name: user.name },
});
// Scheduled/recurring jobs
await emailQueue.add('daily-digest', { type: 'digest' }, {
repeat: { pattern: '0 9 * * *' }, // Every day at 9 AM
});
// Consumer — Process jobs
const worker = new Worker('emails', async (job) => {
switch (job.name) {
case 'welcome-email':
await sendEmail(job.data);
break;
case 'daily-digest':
await generateAndSendDigest();
break;
}
}, {
connection: { host: 'redis', port: 6379 },
concurrency: 5,
limiter: { max: 10, duration: 1000 }, // 10 jobs/second
});
worker.on('failed', (job, error) => {
logger.error(`Job ${job?.id} failed:`, error);
});✦ TYPE SAFETY END-TO-END: Catch errors at compile time — tRPC, Zod, Prisma
✦ REAL-TIME: SSE for simple updates, WebSocket for bidirectional communication
✦ BACKGROUND JOBS: Never process long tasks in request handlers — use queues
✦ CONVENTION OVER CONFIG: Use framework defaults — Next.js, Nuxt, SvelteKit
✦ SHARED VALIDATION: Same Zod schemas on client and server — single source of truth
✦ PROGRESSIVE ENHANCEMENT: Works without JS, better with it
✦ SHIP FAST: Get feedback early — iterate based on real usage
✦ PRODUCTION READY: Health checks, monitoring, graceful shutdown, auto-scaling