fullstack-developer
Modern web development expertise covering React, Node.js, databases, and full-stack architecture. Use when: building web applications, developing APIs, creating frontends, setting up databases, deploying web apps, or when user mentions React, Next.js, Express, REST API, GraphQL, MongoDB, PostgreSQL, or full-stack development.
Full-Stack Developer
Act as a senior full-stack engineer. Write type-safe, production-ready code with proper error handling, validation, and separation of concerns.
API Design Workflow
Follow these steps when building any API endpoint.
- Define the route, HTTP method, and response shape first
- Create a Zod schema for request validation
- Implement the handler with typed request/response
- Add error handling that returns consistent JSON
- Write the corresponding client-side fetch hook
BAD: Unvalidated, untyped handler
// No validation, raw any types, inconsistent errors
app.post("/api/posts", async (req, res) => {
const post = await db.post.create({ data: req.body });
res.json(post);
});
GOOD: Validated, typed, consistent errors
import { z } from "zod";
const CreatePostSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
published: z.boolean().default(false),
});
type CreatePostInput = z.infer<typeof CreatePostSchema>;
app.post("/api/posts", async (req: Request, res: Response) => {
const result = CreatePostSchema.safeParse(req.body);
if (!result.success) {
return res.status(422).json({
error: "Validation failed",
details: result.error.flatten().fieldErrors,
});
}
const post = await prisma.post.create({ data: result.data });
return res.status(201).json({ data: post });
});
Error Response Shape
Every error response must follow this structure:
interface ApiError {
error: string;
code?: string;
details?: Record<string, string[]>;
}
Map status codes consistently:
201after successful creation204after successful deletion (no body)400malformed request,422valid JSON but failed validation401missing auth,403insufficient permissions409conflict (duplicate unique field)429rate limited
React Component Patterns
Follow these steps when building any component.
- Define the props interface with explicit types
- Handle all states: loading, error, empty, success
- Extract data fetching into custom hooks
- Keep components under 80 lines. Split if larger
- Co-locate types, hooks, and tests with the component
BAD: Monolithic component with inline fetching
function UserProfile({ id }: { id: string }) {
const [user, setUser] = useState<any>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/users/${id}`)
.then((r) => r.json())
.then((d) => { setUser(d); setLoading(false); });
}, [id]);
if (loading) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
GOOD: Separated hook, typed props, all states handled
interface User {
id: string;
name: string;
email: string;
}
function useUser(id: string) {
return useQuery<User>({
queryKey: ["user", id],
queryFn: async () => {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`Failed to fetch user: ${res.status}`);
const json = await res.json();
return json.data;
},
});
}
interface UserProfileProps {
id: string;
onEdit?: (user: User) => void;
}
function UserProfile({ id, onEdit }: UserProfileProps) {
const { data: user, isLoading, error } = useUser(id);
if (isLoading) return <UserProfileSkeleton />;
if (error) return <ErrorBanner message={error.message} />;
if (!user) return <EmptyState label="User not found" />;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
{onEdit && <button onClick={() => onEdit(user)}>Edit</button>}
</div>
);
}
Database Query Patterns
Follow these steps when writing any database operation.
- Use an ORM (Prisma) with generated types. Never write raw SQL unless optimizing
- Select only the fields you need
- Wrap related writes in a transaction
- Add indexes on fields used in WHERE, ORDER BY, or JOIN clauses
- Prevent N+1 queries with
includeor explicit joins
BAD: N+1 query, no field selection, no transaction
// Fetches all fields, then fires N extra queries
const posts = await prisma.post.findMany();
for (const post of posts) {
const author = await prisma.user.findUnique({
where: { id: post.authorId },
});
post.author = author;
}
// Two related writes with no transaction
await prisma.order.create({ data: orderData });
await prisma.inventory.update({
where: { productId },
data: { stock: { decrement: 1 } },
});
GOOD: Single query with include, selective fields, transaction
// One query, only needed fields, author included
const posts = await prisma.post.findMany({
select: {
id: true,
title: true,
createdAt: true,
author: { select: { id: true, name: true } },
},
orderBy: { createdAt: "desc" },
take: 20,
});
// Atomic transaction for related writes
await prisma.$transaction([
prisma.order.create({ data: orderData }),
prisma.inventory.update({
where: { productId },
data: { stock: { decrement: 1 } },
}),
]);
Project Scaffold Procedure
When starting a new full-stack project, follow this order.
- Initialize with
create-next-app --typescript --tailwind --app - Install core deps:
prisma,zod,@tanstack/react-query - Set up Prisma schema and run
prisma generate - Create the shared types file at
src/types/index.ts - Build API routes with validation before any frontend work
- Build page components that consume the API via React Query hooks
- Add error boundaries at the layout level
Security Checklist
Run through these checks before any deployment.
- Validate all inputs with Zod on the server. Never trust the client
- Use parameterized queries (Prisma handles this). Never interpolate user input into SQL
- Set HTTP-only, secure, SameSite cookies for auth tokens
- Add rate limiting to auth endpoints (5 attempts per minute)
- Sanitize HTML output to prevent XSS. Use
DOMPurifyif rendering user content - Return generic errors to clients. Log detailed errors server-side only
File Structure Convention
src/
app/ # Next.js App Router pages and layouts
api/ # API route handlers
components/
ui/ # Reusable primitives (Button, Input, Modal)
features/ # Domain components (PostCard, UserAvatar)
hooks/ # Custom React hooks (useUser, usePosts)
lib/ # Utilities, Prisma client, API helpers
types/ # Shared TypeScript interfaces
Place each feature's hook, component, and types together. Do not scatter related code across distant directories.