Building PhotoTech AI: A Unified Platform for 20 AI Image Processing Tools
As a developer, I've always been frustrated with the fragmented landscape of AI image processing tools. Need to remove a background? Go to Remove.bg. Want to upscale an image? Use Upscayl. Need to generate anime art? Find another service. Each tool requires a separate account, different pricing models, and switching between multiple tabs.
So I built PhotoTech AI โ a unified platform that brings together 20 AI-powered image processing tools in one place. In this post, I'll share the technical architecture, implementation details, and lessons learned.
๐ฏ What We Built
PhotoTech AI (https://phototech.shop) is a full-stack web application that provides:
- 20 AI Image Tools: Background removal, upscaling, 2D-to-3D conversion, photo restoration, style transfer, AI generation (anime, characters, comics), and more
- Tiered Membership System: Free, Pro ($9.9/mo), Pro+ ($19.9/mo), Ultra ($39.9/mo) with different tool access and credit consumption rates
- Credit-Based Billing: Flexible credit system where higher-tier members consume fewer credits per operation
- Payment Integration: Seamless payment processing with Creem payment gateway
- Watermark Management: Automatic watermarking for free users, watermark-free for paid members
๐ ๏ธ Tech Stack
- Frontend: Next.js 16, React 19, TypeScript, Tailwind CSS
- Backend: Next.js API Routes, Supabase (Auth + Database)
- Database: PostgreSQL with Prisma ORM
- AI Services: OpenRouter API (GPT-4o, Gemini 2.0 Flash)
- Payment: Creem payment gateway
- Deployment: Vercel
- Analytics: Vercel Analytics + Speed Insights
๐๏ธ Architecture Overview
โโโโโโโโโโโโโโโโโโโ
โ Next.js App โ
โ (Frontend + โ
โ API Routes) โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโดโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ โ โ โ
โโโโโผโโโโ โโโโผโโโโ โโโโโโโผโโโโโโ โโโโโโผโโโโโ
โSupabaseโ โPrismaโ โOpenRouterโ โ Creem โ
โ Auth โ โ DB โ โ API โ โ Payment โ
โโโโโโโโโโ โโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโ
Key Design Decisions
-
Unified API Route: Single
/api/ai/process-imageendpoint that routes to different AI processors based on tool type - Tool Processor Pattern: Abstract base processor with specific implementations for each AI service
- Membership-Based Access Control: Server-side validation to prevent frontend bypassing
- Credit System: Tiered consumption rates (Free: 5 credits, Pro: 4, Pro+: 3, Ultra: 2 per operation)
๐ป Core Implementation
1. Unified Image Processing API
The heart of the system is a single API route that handles all AI image processing:
export async function POST(request: NextRequest) {
// 1. Authenticate user
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
const { imageBase64, textPrompt, feature, options } = await request.json()
// 2. Validate tool access (server-side)
const membershipLevel = await getUserMembershipLevel(user.email)
const allTools = getEnabledTools()
const toolIndex = allTools.findIndex(tool => tool.id === feature)
if (!canUseTool(membershipLevel, toolIndex, allTools.length)) {
return NextResponse.json(
{ error: "Tool not available for your membership level" },
{ status: 403 }
)
}
// 3. Check credits
const creditsCost = getCreditsCostForMembership(membershipLevel)
const currentCredits = await getUserCredits(user.email)
if (currentCredits < creditsCost) {
return NextResponse.json(
{ error: "Insufficient credits" },
{ status: 402 }
)
}
// 4. Process image
const result = await processImage({
toolId: feature,
imageBase64,
textPrompt,
options,
})
// 5. Deduct credits
await deductCredits(user.email, creditsCost, `Used ${feature}`)
// 6. Apply watermark if needed
const needsWatermark = requiresWatermark(membershipLevel)
return NextResponse.json({
imageUrl: result.imageUrl,
needsWatermark,
creditsUsed: creditsCost,
creditsRemaining: currentCredits - creditsCost,
})
}
2. Tool Processor Pattern
We use a processor pattern to abstract different AI services:
export abstract class BaseProcessor {
abstract process(
imageBase64: string,
options?: Record<string, any>
): Promise<ProcessResult>
protected async callOpenRouter(
model: string,
messages: any[],
imageBase64?: string
): Promise<string> {
// Unified OpenRouter API call
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model,
messages,
// ... image handling
}),
})
return await response.json()
}
}
Each tool has its own processor implementation:
export class OpenRouterImageProcessor extends BaseProcessor {
async process(imageBase64: string, options?: Record<string, any>) {
const model = this.getModel() // e.g., "google/gemini-2.0-flash-exp"
const messages = [
{
role: "user",
content: [
{ type: "text", text: this.getPrompt() },
{ type: "image_url", image_url: { url: `data:image/png;base64,${imageBase64}` } }
]
}
]
const result = await this.callOpenRouter(model, messages, imageBase64)
return this.extractImageUrl(result)
}
}
3. Tiered Credit System
One interesting challenge was implementing tiered credit consumption. Higher-tier members get better value:
export function getCreditsCostForMembership(
membershipLevel: MembershipLevel | null | undefined
): number {
if (!membershipLevel || membershipLevel === "free") return 5
if (membershipLevel === "pro") return 4
if (membershipLevel === "proplus") return 3
if (membershipLevel === "ultra") return 2
return 5
}
This creates a natural incentive to upgrade: Ultra members get 2.5x more operations per credit than free users.
4. Membership-Based Tool Access
Tools are ordered by complexity/value, and access is controlled by membership level:
export function canUseTool(
membershipLevel: MembershipLevel | null | undefined,
toolIndex: number,
totalTools: number
): boolean {
const limit = getToolLimitForMembership(membershipLevel)
// Ultra members can use all tools
if (membershipLevel === "ultra") {
return true
}
// Others can only use first N tools
return toolIndex < limit
}
Tool Distribution:
- Free: First 3 tools (with watermark)
- Pro: First 8 tools
- Pro+: First 15 tools
- Ultra: All 20 tools (no watermark)
5. Payment Integration with Creem
We integrated Creem payment gateway with webhook support:
export async function POST(request: NextRequest) {
const event = JSON.parse(await request.text())
switch (event.type) {
case "payment.succeeded":
case "checkout.session.completed":
const { customerEmail, amountTotal } = extractPaymentData(event)
// Calculate credits (1:10 ratio)
const credits = calculateCreditsFromAmount(amountTotal)
await addCredits(customerEmail, credits, "Payment")
// Set membership level based on product
const membershipLevel = mapProductToMembership(event.product_id)
await setUserMembershipLevel(customerEmail, membershipLevel)
return NextResponse.json({ success: true })
}
}
6. Client-Side Watermark Application
For free users, we apply watermarks client-side to reduce server load:
const applyWatermark = async (imageUrl: string) => {
if (!data.needsWatermark) return imageUrl
// Dynamically import watermark module (client-side only)
const { addWatermarkToImage } = await import("@/lib/watermark")
const watermarkedImage = await addWatermarkToImage(
imageUrl,
"PhotoTech AI",
{ fontSize: 24, opacity: 0.7 }
)
return watermarkedImage
}
๐ง Challenges & Solutions
Challenge 1: Handling Multiple AI Providers
Problem: Different AI services have different APIs, response formats, and capabilities.
Solution: Abstract processor pattern with a unified interface. Each tool can use the best AI model for its purpose (GPT-4o for analysis, Gemini 2.0 for generation, etc.).
Challenge 2: Server-Side Rendering with useSearchParams
Problem: Next.js 16 requires useSearchParams() to be wrapped in Suspense boundaries.
Solution: Wrap components using useSearchParams() in Suspense:
export default function Home() {
return (
<main>
<Suspense fallback={null}>
<PaymentCallbackHandler />
</Suspense>
{/* ... */}
</main>
)
}
Challenge 3: Test Environment Configuration
Problem: Need to disable credit consumption and tool limits during development.
Solution: Environment-based flags:
const isTestMode =
process.env.NODE_ENV === 'development' ||
process.env.ENABLE_TEST_MODE === 'true'
if (isTestMode) {
// Skip credit checks, allow all tools, no watermarks
}
Challenge 4: Payment Webhook Reliability
Problem: Payment webhooks might fail or arrive out of order.
Solution: Implemented client-side payment verification as a fallback:
useEffect(() => {
const verifyPayment = async () => {
const sessionId = searchParams.get("session_id")
if (sessionId) {
await fetch("/api/creem/verify-payment", {
method: "POST",
body: JSON.stringify({ sessionId }),
})
}
}
verifyPayment()
}, [])
๐ Database Schema
model User {
id String @id @default(uuid())
email String @unique
name String?
image String?
credits Int @default(20)
membershipLevel MembershipLevel @default(FREE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum MembershipLevel {
FREE
PRO
PROPLUS
ULTRA
}
๐จ Frontend Architecture
-
Component Structure: Feature-based organization (
components/image-upload.tsx,components/pricing/) - State Management: React hooks + Supabase session management
- Styling: Tailwind CSS with shadcn/ui components
- Image Handling: Next.js Image component with client-side watermarking
๐ Performance Optimizations
- Client-Side Watermarking: Reduces server load for free users
- Dynamic Imports: Lazy load watermark module only when needed
- Image Optimization: Next.js Image component with automatic optimization
- API Route Caching: Cache tool configurations and membership data
- Vercel Analytics: Monitor performance and user behavior
๐ฎ Future Improvements
- Batch Processing: Allow users to process multiple images at once
- API Access: Provide REST API for developers
- More AI Models: Integrate additional providers (Stability AI, Midjourney API)
- Image History: Save processed images for users
- Collaboration Features: Share and collaborate on images
๐ Key Takeaways
- Unified API Design: Single endpoint for all tools simplifies client code and maintenance
- Server-Side Validation: Always validate permissions and credits on the server
- Flexible Credit System: Tiered consumption creates natural upgrade incentives
- Processor Pattern: Abstracting AI providers makes it easy to add new tools
- Test Mode: Environment-based feature flags are essential for development
๐ Resources
- Live Site: https://phototech.shop
- Tech Stack: Next.js, React, Supabase, Prisma, OpenRouter
- Payment: Creem payment gateway
๐ฌ Feedback Welcome!
I'd love to hear your thoughts on the architecture, implementation, or suggestions for improvements. Feel free to reach out or check out the live site!
Tags: #nextjs #react #typescript #ai
Top comments (1)
Live Site: phototech.shop