Back to Blog
Tutorials 13 min read February 19, 2025

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

DevForge Team

AI Development Educators

Developer celebrating a successful deployment on laptop

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)

bash
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-highlighter

Create .env.local:

bash
ANTHROPIC_API_KEY=sk-ant-your-key-here

Add to .gitignore:

text
.env.local

Step 3: The API Route (45 minutes)

This is the heart of the app. The API route calls Claude and streams the response:

typescript
// 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)

typescript
// 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)

bash
# 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 --prod

Your 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:

  1. API key security — Key stays on server, never in browser
  2. Streaming responses — Real-time output instead of waiting
  3. System prompts — Controlling AI behavior and output format
  4. Input validation — Preventing bad inputs before sending to Claude
  5. Error handling — Graceful failure with user feedback
  6. 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.

#Tutorial#Claude API#Next.js#Deployment#Vercel