ignitionstack.pro v1.0 is out! Read the announcement →
Skip to Content

API Documentation

ignitionstack.pro uses Next.js App Router with Server Actions and API Routes for backend functionality.

Architecture

Server Actions are the primary way to handle data mutations. They provide:

// server/actions/create-post.ts 'use server' import { createClient } from '@/lib/supabase/server' import { ActionResult } from '@/lib/action-result' import { revalidatePath } from 'next/cache' export async function createPost(formData: FormData): Promise<ActionResult<Post>> { const supabase = await createClient() const { data, error } = await supabase .from('posts') .insert({ title: formData.get('title') as string, content: formData.get('content') as string, }) .select() .single() if (error) { return { success: false, error: error.message } } revalidatePath('/blog') return { success: true, data } }

Server Queries (Data Fetching)

Server-side data fetching with caching:

// server/queries/get-posts.ts import { createClient } from '@/lib/supabase/server' import { cache } from 'react' import { unstable_cache } from 'next/cache' export const getPosts = cache(async () => { const supabase = await createClient() const { data, error } = await supabase .from('posts') .select('*') .eq('published', true) .order('created_at', { ascending: false }) if (error) throw error return data }) // With cache tag for revalidation export const getPostsCached = unstable_cache( async () => getPosts(), ['posts'], { revalidate: 60, tags: ['posts'] } )

API Routes (Webhooks)

API Routes are used for:

API Endpoints

Stripe Webhooks

EndpointMethodDescription
/api/stripe/webhookPOSTHandle Stripe events
/api/stripe/create-checkoutPOSTCreate checkout session
// api/stripe/webhook/route.ts import { NextResponse } from 'next/server' import Stripe from 'stripe' import { stripe } from '@/lib/stripe' export async function POST(request: Request) { const body = await request.text() const signature = request.headers.get('stripe-signature')! try { const event = stripe.webhooks.constructEvent( body, signature, process.env.STRIPE_WEBHOOK_SECRET_KEY! ) switch (event.type) { case 'checkout.session.completed': // Handle successful payment break case 'customer.subscription.updated': // Handle subscription change break } return NextResponse.json({ received: true }) } catch (error) { return NextResponse.json( { error: 'Webhook error' }, { status: 400 } ) } }

AI Chat API

EndpointMethodDescription
/api/ai/chatPOSTStream chat response
/api/ai/uploadPOSTUpload documents for RAG
/api/ai/sharePOSTShare conversation
// api/ai/chat/route.ts import { NextResponse } from 'next/server' import { streamText } from '@/lib/ai' export async function POST(request: Request) { const { messages, provider, model } = await request.json() const stream = await streamText({ messages, provider, model, }) return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }, }) }

Email Webhooks

EndpointMethodDescription
/api/resend/webhookPOSTHandle email events

Server Function Patterns

Action Result Pattern

All server actions return a standardized result:

// lib/action-result.ts export type ActionResult<T> = | { success: true; data: T } | { success: false; error: string } // Usage const result = await createPost(formData) if (result.success) { console.log('Created:', result.data) } else { console.error('Error:', result.error) }

Admin Action Wrapper

Protect admin-only actions:

// lib/admin-action-wrapper.ts import { isAdmin } from '@/lib/admin-auth' export function withAdminAuth<T extends (...args: any[]) => Promise<ActionResult<any>>>( action: T ): T { return (async (...args: Parameters<T>) => { if (!(await isAdmin())) { return { success: false, error: 'Unauthorized' } } return action(...args) }) as T } // Usage export const deleteUser = withAdminAuth(async (userId: string) => { // Only admins can delete users })

Repository Pattern

Data access is abstracted through repositories:

// lib/repositories/user-repository.ts import { createClient, createAdminClient } from '@/lib/supabase/server' export const userRepository = { async findById(id: string) { const supabase = await createClient() const { data, error } = await supabase .from('users') .select('*') .eq('id', id) .single() if (error) return null return data }, async create(input: CreateUserInput) { const supabase = createAdminClient() // Bypass RLS const { data, error } = await supabase .from('users') .insert(input) .select() .single() if (error) throw error return data }, }

Server Organization

src/app/server/ ├── ai/ # AI-related queries │ ├── conversations.ts │ └── messages.ts ├── blog/ # Blog queries │ ├── posts.ts │ └── categories.ts ├── products/ # E-commerce │ └── products.ts ├── projects/ # Project management │ └── projects.ts ├── users/ # User management │ └── users.ts └── insights/ # Analytics └── insights.ts

Rate Limiting

API routes include rate limiting:

// lib/rate-limit.ts import { Ratelimit } from '@upstash/ratelimit' import { Redis } from '@upstash/redis' const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, '10 s'), }) // Usage in API route export async function POST(request: Request) { const ip = request.headers.get('x-forwarded-for') ?? 'anonymous' const { success } = await ratelimit.limit(ip) if (!success) { return NextResponse.json( { error: 'Too many requests' }, { status: 429 } ) } // Continue... }

Rate limiting is applied to sensitive endpoints like authentication, payments, and AI chat to prevent abuse.

Error Handling

Supabase Error Handler

// lib/supabase/error-handler.ts import { PostgrestError } from '@supabase/supabase-js' export function handleSupabaseError(error: PostgrestError): string { switch (error.code) { case '23505': return 'This record already exists' case '23503': return 'Referenced record not found' case '42501': return 'Permission denied' default: return error.message } }

Logging

Structured logging with Pino:

import { logger } from '@/lib/logger' // In server actions logger.info({ userId, action: 'create_post' }, 'User created post') logger.error({ error, userId }, 'Failed to create post')

Best Practices

Use Server Actions for Mutations

Server Actions provide better security and DX than API routes for form submissions.

Cache Aggressively

Use cache() and unstable_cache for data fetching to reduce database load.

Validate Input

Always validate user input with Zod schemas:

import { z } from 'zod' const CreatePostSchema = z.object({ title: z.string().min(1).max(200), content: z.string().min(1), })

Handle Errors Gracefully

Return ActionResult with meaningful error messages instead of throwing.

Use Repository Pattern

Abstract database operations for cleaner code and easier testing.

Next Steps