From Zero to Deployed: Ship Your First AI App This Weekend
A concrete, step-by-step guide to building and deploying your first real AI application in a weekend. Stack selection, code scaffold, and deployment all included.

DevForge Team
AI Development Educators

What You'll Build
By the end of this weekend project, you'll have deployed a real AI-powered web application: a Code Review Assistant that:
- Accepts code in any language
- Analyzes it for bugs, security issues, and improvements
- Streams the review in real-time (typing effect)
- Suggests specific fixes with code examples
- Is deployed and accessible via a public URL
This is a genuinely useful tool. More importantly, it demonstrates every key skill you need for building AI applications: API integration, streaming, frontend UI, and deployment.
Time estimate: 4-6 hours total.
Step 1: Stack Selection (30 minutes)
For this project, we're using:
- Framework: Next.js 14 App Router (best AI integration)
- Styling: Tailwind CSS (rapid UI development)
- AI: Anthropic Claude claude-haiku-3-5 (fast + cheap = ideal for demos)
- Deployment: Vercel (zero-config, free tier)
- Syntax highlighting: Prism.js or react-syntax-highlighter
Why this stack?
Next.js handles server-side API routes (protecting your API key), has excellent streaming support, and deploys on Vercel in one command. Claude claude-haiku-3-5 is fast enough to feel responsive and cheap enough that demos won't cost you money.
Step 2: Project Scaffold (45 minutes)
npx create-next-app@latest code-review-ai \
--typescript \
--tailwind \
--app \
--src-dir \
--import-alias "@/*"
cd code-review-ai
npm install @anthropic-ai/sdk react-syntax-highlighter
npm install -D @types/react-syntax-highlighterCreate .env.local:
ANTHROPIC_API_KEY=sk-ant-your-key-hereAdd to .gitignore:
.env.localStep 3: The API Route (45 minutes)
This is the heart of the app. The API route calls Claude and streams the response:
// src/app/api/review/route.ts
import Anthropic from "@anthropic-ai/sdk";
import { NextRequest } from "next/server";
const client = new Anthropic();
const SYSTEM_PROMPT = `You are an expert code reviewer with deep experience in
software security, performance optimization, and best practices.
When reviewing code, structure your response as follows:
## Summary
Brief 2-3 sentence overview of the code and its quality.
## Issues Found
For each issue:
**[Severity: Critical/High/Medium/Low]** Issue title
- Description: What the problem is
- Impact: Why it matters
- Fix: Specific code showing how to resolve it
## Positive Aspects
What the code does well.
## Recommended Improvements
Non-critical suggestions for making the code better.
Be specific, actionable, and include code examples for fixes.`;
export async function POST(req: NextRequest) {
try {
const { code, language } = await req.json();
if (!code || typeof code !== "string") {
return Response.json({ error: "Code is required" }, { status: 400 });
}
const truncatedCode = code.slice(0, 10000); // Safety limit
const stream = await client.messages.stream({
model: "claude-haiku-3-5",
max_tokens: 2048,
system: SYSTEM_PROMPT,
messages: [{
role: "user",
content: `Please review this ${language || "code"}:
\`\`\`${language || ""}
${truncatedCode}
\`\`\``
}],
});
const readableStream = new ReadableStream({
async start(controller) {
try {
for await (const chunk of stream) {
if (
chunk.type === "content_block_delta" &&
chunk.delta.type === "text_delta"
) {
controller.enqueue(
new TextEncoder().encode(chunk.delta.text)
);
}
}
} finally {
controller.close();
}
},
});
return new Response(readableStream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "no-cache",
},
});
} catch (error) {
console.error("Review error:", error);
return Response.json(
{ error: "Failed to review code" },
{ status: 500 }
);
}
}Step 4: The UI (2 hours)
// src/app/page.tsx
"use client";
import { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
const LANGUAGES = [
"javascript", "typescript", "python", "java", "c", "cpp",
"csharp", "go", "rust", "php", "ruby", "sql", "bash"
];
const SAMPLE_CODE = `function getUserData(userId) {
const query = "SELECT * FROM users WHERE id = " + userId;
return db.execute(query);
}`;
export default function HomePage() {
const [code, setCode] = useState(SAMPLE_CODE);
const [language, setLanguage] = useState("javascript");
const [review, setReview] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleReview() {
if (!code.trim() || isLoading) return;
setIsLoading(true);
setReview("");
setError(null);
try {
const response = await fetch("/api/review", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code, language }),
});
if (!response.ok) {
throw new Error("Review failed");
}
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (!reader) throw new Error("No response stream");
while (true) {
const { done, value } = await reader.read();
if (done) break;
setReview(prev => prev + decoder.decode(value, { stream: true }));
}
} catch (err) {
setError("Failed to review code. Please try again.");
} finally {
setIsLoading(false);
}
}
return (
<div className="min-h-screen bg-gray-950 text-gray-100">
{/* Header */}
<header className="border-b border-gray-800 px-6 py-4">
<div className="max-w-6xl mx-auto flex items-center justify-between">
<div>
<h1 className="text-xl font-bold">Code Review AI</h1>
<p className="text-gray-400 text-sm">Powered by Claude claude-haiku-3-5</p>
</div>
<a
href="https://devforgeacademy.com/tutorials/claude-api"
className="text-sm text-violet-400 hover:text-violet-300"
target="_blank"
>
Learn to build this →
</a>
</div>
</header>
<main className="max-w-6xl mx-auto px-6 py-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Left: Code Input */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="font-semibold text-gray-200">Your Code</h2>
<select
value={language}
onChange={e => setLanguage(e.target.value)}
className="bg-gray-800 border border-gray-700 rounded px-3 py-1.5 text-sm"
>
{LANGUAGES.map(lang => (
<option key={lang} value={lang}>
{lang.charAt(0).toUpperCase() + lang.slice(1)}
</option>
))}
</select>
</div>
<textarea
value={code}
onChange={e => setCode(e.target.value)}
className="w-full h-80 bg-gray-900 border border-gray-700 rounded-lg p-4
font-mono text-sm text-gray-100 resize-none focus:outline-none
focus:border-violet-500"
placeholder="Paste your code here..."
spellCheck={false}
/>
<button
onClick={handleReview}
disabled={isLoading || !code.trim()}
className="w-full py-3 bg-violet-600 hover:bg-violet-500 disabled:bg-gray-700
disabled:text-gray-400 text-white font-semibold rounded-lg
transition-colors duration-200"
>
{isLoading ? (
<span className="flex items-center justify-center gap-2">
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"
className="opacity-25"/>
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
className="opacity-75"/>
</svg>
Reviewing...
</span>
) : "Review Code →"}
</button>
</div>
{/* Right: Review Output */}
<div className="space-y-4">
<h2 className="font-semibold text-gray-200">Review</h2>
<div className="h-96 bg-gray-900 border border-gray-700 rounded-lg p-4
overflow-y-auto">
{error && (
<div className="text-red-400 text-sm">{error}</div>
)}
{!review && !isLoading && !error && (
<div className="h-full flex items-center justify-center text-gray-500 text-sm">
Paste code and click "Review Code" to get started
</div>
)}
{review && (
<div className="prose prose-invert prose-sm max-w-none">
<pre className="whitespace-pre-wrap text-sm text-gray-200 font-sans">
{review}
{isLoading && (
<span className="inline-block w-1 h-4 bg-violet-400 animate-pulse ml-0.5" />
)}
</pre>
</div>
)}
</div>
</div>
</div>
</main>
</div>
);
}Step 5: Deploy (15 minutes)
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel
# Add environment variable
vercel env add ANTHROPIC_API_KEY
# Paste your key when prompted
# Deploy to production
vercel --prodYour app is now live at your-project.vercel.app.
Step 6: What to Build Next
Now that you have the foundation:
Easy Wins (This Weekend)
- Add syntax highlighting to the code output
- Support file uploads (.py, .js, .ts files)
- Add "Copy review" button
- Show token usage and estimated cost
Slightly More Complex
- Add conversation history (ask follow-up questions)
- Support multiple files review
- Save reviews to Supabase with auth
- Add a "Explain this code" mode alongside review
Production Features
- Rate limiting (1 review per minute per IP)
- Error boundaries and proper error messages
- Analytics (track which languages are reviewed)
- Share URL for reviews
What You've Learned
By completing this project, you've implemented:
- API key security — Key stays on server, never in browser
- Streaming responses — Real-time output instead of waiting
- System prompts — Controlling AI behavior and output format
- Input validation — Preventing bad inputs before sending to Claude
- Error handling — Graceful failure with user feedback
- Deployment — Getting AI apps to production on Vercel
These are the core skills for building AI applications. Everything else — RAG, agents, multimodal — builds on this foundation.
Ship something real this weekend. The confidence that comes from deploying your first AI app compounds.