By Sagar Shankaran, Founder of CallSphere
A practical guide to using Claude Code across the full stack — frontend React/Next.js, backend APIs, databases, DevOps, and end-to-end feature implementation.
Key takeaways
Full-stack development requires context switching between languages, frameworks, and layers. A single feature might touch a React component, a Next.js API route, a database migration, and a Kubernetes deployment manifest. Traditional AI coding tools struggle with this breadth because they optimize for single-file or single-language completion.
Claude Code's agentic architecture makes it uniquely suited for full-stack work. It can read your frontend code to understand the data shape a component expects, then switch to your backend to implement the matching API endpoint, create the database migration, and update the deployment config — all in one conversation.
The CLAUDE.md file is your most important configuration for full-stack projects. A well-written memory file prevents Claude from generating code that clashes with your existing patterns.
flowchart LR
USER(["User message"])
LOOP{"messages.create<br/>agent loop"}
THINK["Extended thinking<br/>optional"]
TOOL{"stop_reason<br/>tool_use?"}
EXEC["Execute tool<br/>append tool_result"]
DONE(["stop_reason<br/>end_turn"])
USER --> LOOP --> THINK --> TOOL
TOOL -->|Yes| EXEC --> LOOP
TOOL -->|No| DONE
style LOOP fill:#4f46e5,stroke:#4338ca,color:#fff
style THINK fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style DONE fill:#059669,stroke:#047857,color:#fff
# Project: SaaSApp
## Architecture
- Frontend: Next.js 14 (App Router), TypeScript, Tailwind CSS
- Backend: FastAPI (Python 3.12), SQLAlchemy 2.0
- Database: PostgreSQL 16 with Alembic migrations
- Cache: Redis 7
- Deployment: K8s (k3s) with hostPath volumes
## Frontend Conventions
- Use server components by default, client components only when needed
- All API calls go through lib/api.ts using fetch
- Forms use react-hook-form with zod validation
- State management: React Query for server state, zustand for client state
- Component structure: components/<Feature>/<Component>.tsx
## Backend Conventions
- API routes: app/api/v1/<resource>.py
- Business logic: app/services/<resource>_service.py
- Database models: app/models/<resource>.py
- Pydantic schemas: app/schemas/<resource>.py
- All endpoints require authentication except those marked public
- Use async/await everywhere — no sync database calls
## Database
- Migrations: alembic revision --autogenerate -m "description"
- Always add indexes for foreign keys and frequently queried columns
- Use UUID primary keys
- Soft delete (deleted_at timestamp) for user-facing resources
## Testing
- Frontend: vitest + react-testing-library
- Backend: pytest with async fixtures
- Run frontend tests: cd frontend && npm test
- Run backend tests: cd backend && pytest -x
Let's walk through how Claude Code handles a full-stack feature: adding a "team invitations" system.
You: Create a team invitations feature. Users should be able to invite others by email.
Invitations expire after 7 days. The invited user sees pending invitations after login.
Claude Code will start by examining your existing database models:
# Claude Code reads your models directory, then creates:
# backend/app/models/invitation.py
from sqlalchemy import Column, String, DateTime, ForeignKey, Enum
from sqlalchemy.dialects.postgresql import UUID
from app.models.base import Base
import uuid
from datetime import datetime, timedelta
import enum
class InvitationStatus(str, enum.Enum):
PENDING = "pending"
ACCEPTED = "accepted"
EXPIRED = "expired"
REVOKED = "revoked"
class Invitation(Base):
__tablename__ = "invitations"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
team_id = Column(UUID(as_uuid=True), ForeignKey("teams.id"), nullable=False, index=True)
inviter_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
email = Column(String(255), nullable=False, index=True)
status = Column(Enum(InvitationStatus), default=InvitationStatus.PENDING, nullable=False)
expires_at = Column(DateTime, default=lambda: datetime.utcnow() + timedelta(days=7))
created_at = Column(DateTime, default=datetime.utcnow)
accepted_at = Column(DateTime, nullable=True)
Claude Code then creates the service and routes:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for IT support in your browser — 60 seconds, no signup.
# backend/app/services/invitation_service.py
from datetime import datetime
from sqlalchemy import select, and_
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.invitation import Invitation, InvitationStatus
from app.schemas.invitation import CreateInvitationRequest
from fastapi import HTTPException
class InvitationService:
def __init__(self, db: AsyncSession):
self.db = db
async def create_invitation(
self, team_id: str, inviter_id: str, request: CreateInvitationRequest
) -> Invitation:
# Check for existing pending invitation
existing = await self.db.execute(
select(Invitation).where(
and_(
Invitation.team_id == team_id,
Invitation.email == request.email,
Invitation.status == InvitationStatus.PENDING,
Invitation.expires_at > datetime.utcnow(),
)
)
)
if existing.scalar_one_or_none():
raise HTTPException(409, "An active invitation already exists for this email")
invitation = Invitation(
team_id=team_id,
inviter_id=inviter_id,
email=request.email,
)
self.db.add(invitation)
await self.db.commit()
await self.db.refresh(invitation)
return invitation
async def get_pending_for_email(self, email: str) -> list[Invitation]:
result = await self.db.execute(
select(Invitation).where(
and_(
Invitation.email == email,
Invitation.status == InvitationStatus.PENDING,
Invitation.expires_at > datetime.utcnow(),
)
)
)
return list(result.scalars().all())
Claude Code then switches to the frontend:
// frontend/components/Team/InviteMemberDialog.tsx
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "@/lib/api";
const inviteSchema = z.object({
email: z.string().email("Please enter a valid email address"),
});
type InviteForm = z.infer<typeof inviteSchema>;
export function InviteMemberDialog({ teamId, onClose }: {
teamId: string;
onClose: () => void;
}) {
const queryClient = useQueryClient();
const { register, handleSubmit, formState: { errors } } = useForm<InviteForm>({
resolver: zodResolver(inviteSchema),
});
const mutation = useMutation({
mutationFn: (data: InviteForm) =>
api.post(`/api/v1/teams/${teamId}/invitations`, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["invitations", teamId] });
onClose();
},
});
return (
<form onSubmit={handleSubmit((data) => mutation.mutate(data))}>
<div className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium">
Email Address
</label>
<input
{...register("email")}
type="email"
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2"
placeholder="colleague@company.com"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email.message}</p>
)}
</div>
<button
type="submit"
disabled={mutation.isPending}
className="w-full rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{mutation.isPending ? "Sending..." : "Send Invitation"}
</button>
{mutation.isError && (
<p className="text-sm text-red-600">
{mutation.error instanceof Error ? mutation.error.message : "Failed to send invitation"}
</p>
)}
</div>
</form>
);
}
Claude Code writes tests for both layers:
# backend/tests/test_invitations.py
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
async def test_create_invitation(client: AsyncClient, auth_headers: dict):
response = await client.post(
"/api/v1/teams/test-team-id/invitations",
json={"email": "newuser@example.com"},
headers=auth_headers,
)
assert response.status_code == 201
data = response.json()
assert data["email"] == "newuser@example.com"
assert data["status"] == "pending"
@pytest.mark.asyncio
async def test_duplicate_invitation_rejected(client: AsyncClient, auth_headers: dict):
# First invitation
await client.post(
"/api/v1/teams/test-team-id/invitations",
json={"email": "duplicate@example.com"},
headers=auth_headers,
)
# Duplicate should fail
response = await client.post(
"/api/v1/teams/test-team-id/invitations",
json={"email": "duplicate@example.com"},
headers=auth_headers,
)
assert response.status_code == 409
Claude Code seamlessly context-switches between languages. In a single session, you might:
Claude Code handles all of this because it does not rely on language-specific tooling — it reads files, understands code at a semantic level, and edits with precision regardless of the language.
Claude Code integrates well with migration tools:
You: Add a "role" column to the invitations table with values "member" and "admin", defaulting to "member".
Claude Code will:
alembic revision --autogenerate -m "add_role_to_invitations"Claude Code reads and writes infrastructure files just as naturally as application code:
Still reading? Stop comparing — try CallSphere live.
See the IT support AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
# Claude Code can generate and modify:
# - Dockerfiles
# - docker-compose.yml
# - Kubernetes manifests (Deployments, Services, Ingress)
# - GitHub Actions workflows
# - Terraform configurations
# - Nginx/Caddy configs
Example prompt: "Add a health check endpoint to the backend and update the Kubernetes deployment to use it as a liveness probe."
Claude Code will create the /health endpoint in your FastAPI app, then update the Kubernetes Deployment manifest with the appropriate livenessProbe and readinessProbe configuration.
Keep your CLAUDE.md updated — Every time you adopt a new pattern, add it to CLAUDE.md so Claude follows it in future sessions.
Work feature-by-feature — Ask Claude to implement one complete feature at a time, across all layers, rather than asking for "all the backend endpoints" then "all the frontend components."
Use /compact between features — Full-stack features generate a lot of context. Compact the conversation before starting the next feature.
Let Claude run the tests — After implementing a feature, say "run the tests and fix any failures." Claude Code excels at the fix-test loop.
Review database migrations carefully — Always review auto-generated migrations before running them. Claude Code can help review them too: "Review this migration for potential data loss."
Claude Code's ability to work across the full stack in a single conversation — reading frontend code, implementing backend logic, writing migrations, and updating infrastructure — makes it one of the most effective tools for full-stack developers. The key is a well-structured CLAUDE.md that captures your project's conventions across all layers.

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Enterprise CIO Guide perspective on Claude Code 2.1 ships background agents, sub-agent spawning, and a hooks API that turn it into a true multi-agent coding platform.
Claude has owned developer mindshare since 3.5 Sonnet. Is the coding edge real? A benchmark and tooling examination of where Claude actually leads in 2026.
k3s + hostPath volumes give CallSphere agent hot-reload without redeploys. Vapi customers ship through their pipeline. Engineering velocity matters.
SMB Founder Playbook perspective on Claude Code 2.1 ships background agents, sub-agent spawning, and a hooks API that turn it into a true multi-agent coding platform.
Healthcare Practice Use Case perspective on Claude Code 2.1 ships background agents, sub-agent spawning, and a hooks API that turn it into a true multi-agent coding platform.
Real Estate and Property Management Lens perspective on Claude Code 2.1 ships background agents, sub-agent spawning, and a hooks API that turn it into a true multi-agent coding platform.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI