# SiliconPin Code Patterns and Examples This document provides common patterns and examples for extending the SiliconPin platform. ## Table of Contents - [Authentication Patterns](#authentication-patterns) - [API Route Patterns](#api-route-patterns) - [Database Patterns](#database-patterns) - [Form Handling](#form-handling) - [Component Patterns](#component-patterns) - [State Management](#state-management) - [Error Handling](#error-handling) ## Authentication Patterns ### Using Authentication in Components ```typescript // components/ProfileCard.tsx 'use client'; import { useAuth } from '@/contexts/AuthContext'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; export function ProfileCard() { const { user, isLoading, logout } = useAuth(); if (isLoading) { return
Loading...
; } if (!user) { return
Please log in to view your profile.
; } return ( Welcome, {user.name}!

Email: {user.email}

Member since: {new Date(user.createdAt).toLocaleDateString()}

); } ``` ### Protected Route Component ```typescript // components/ProtectedRoute.tsx 'use client'; import { useAuth } from '@/contexts/AuthContext'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; interface ProtectedRouteProps { children: React.ReactNode; redirectTo?: string; } export function ProtectedRoute({ children, redirectTo = '/auth' }: ProtectedRouteProps) { const { user, isLoading } = useAuth(); const router = useRouter(); useEffect(() => { if (!isLoading && !user) { router.push(redirectTo); } }, [user, isLoading, router, redirectTo]); if (isLoading) { return (
Loading...
); } if (!user) { return null; // Will redirect } return <>{children}; } ``` ## API Route Patterns ### Basic CRUD API Route ```typescript // app/api/posts/route.ts import { authMiddleware } from '@/lib/auth-middleware' import { connectDB } from '@/lib/mongodb' import { Post } from '@/models/post' import { z } from 'zod' const CreatePostSchema = z.object({ title: z.string().min(1, 'Title is required'), content: z.string().min(10, 'Content must be at least 10 characters'), tags: z.array(z.string()).optional(), }) // GET /api/posts - Get all posts export async function GET(request: Request) { try { await connectDB() const url = new URL(request.url) const page = parseInt(url.searchParams.get('page') || '1') const limit = parseInt(url.searchParams.get('limit') || '10') const skip = (page - 1) * limit const posts = await Post.find() .sort({ createdAt: -1 }) .skip(skip) .limit(limit) .populate('author', 'name email') const total = await Post.countDocuments() return Response.json({ success: true, data: { posts, pagination: { page, limit, total, pages: Math.ceil(total / limit), }, }, }) } catch (error) { return Response.json({ success: false, error: 'Failed to fetch posts' }, { status: 500 }) } } // POST /api/posts - Create new post export async function POST(request: Request) { try { const user = await authMiddleware(request) if (!user) { return Response.json({ success: false, error: 'Authentication required' }, { status: 401 }) } const body = await request.json() const validatedData = CreatePostSchema.parse(body) await connectDB() const post = new Post({ ...validatedData, author: user.id, }) await post.save() await post.populate('author', 'name email') return Response.json( { success: true, data: { post }, message: 'Post created successfully', }, { status: 201 } ) } catch (error) { if (error instanceof z.ZodError) { return Response.json( { success: false, error: 'Validation failed', details: error.errors.map((err) => ({ field: err.path.join('.'), message: err.message, })), }, { status: 400 } ) } return Response.json({ success: false, error: 'Failed to create post' }, { status: 500 }) } } ``` ### Dynamic API Route with ID ```typescript // app/api/posts/[id]/route.ts import { authMiddleware } from '@/lib/auth-middleware' import { connectDB } from '@/lib/mongodb' import { Post } from '@/models/post' import mongoose from 'mongoose' interface RouteParams { params: { id: string } } // GET /api/posts/[id] - Get single post export async function GET(request: Request, { params }: RouteParams) { try { if (!mongoose.Types.ObjectId.isValid(params.id)) { return Response.json({ success: false, error: 'Invalid post ID' }, { status: 400 }) } await connectDB() const post = await Post.findById(params.id).populate('author', 'name email') if (!post) { return Response.json({ success: false, error: 'Post not found' }, { status: 404 }) } return Response.json({ success: true, data: { post }, }) } catch (error) { return Response.json({ success: false, error: 'Failed to fetch post' }, { status: 500 }) } } // DELETE /api/posts/[id] - Delete post export async function DELETE(request: Request, { params }: RouteParams) { try { const user = await authMiddleware(request) if (!user) { return Response.json({ success: false, error: 'Authentication required' }, { status: 401 }) } if (!mongoose.Types.ObjectId.isValid(params.id)) { return Response.json({ success: false, error: 'Invalid post ID' }, { status: 400 }) } await connectDB() const post = await Post.findById(params.id) if (!post) { return Response.json({ success: false, error: 'Post not found' }, { status: 404 }) } // Check if user owns the post if (post.author.toString() !== user.id) { return Response.json( { success: false, error: 'Not authorized to delete this post' }, { status: 403 } ) } await Post.findByIdAndDelete(params.id) return Response.json({ success: true, message: 'Post deleted successfully', }) } catch (error) { return Response.json({ success: false, error: 'Failed to delete post' }, { status: 500 }) } } ``` ## Database Patterns ### Mongoose Model with Validation ```typescript // models/post.ts import mongoose, { Document, Schema } from 'mongoose' import { z } from 'zod' // Zod schema for validation export const PostValidationSchema = z.object({ title: z.string().min(1, 'Title is required').max(200, 'Title too long'), content: z.string().min(10, 'Content must be at least 10 characters'), tags: z.array(z.string()).max(10, 'Maximum 10 tags allowed').optional(), published: z.boolean().default(false), }) // TypeScript interface export interface IPost extends Document { title: string content: string tags?: string[] published: boolean author: mongoose.Types.ObjectId createdAt: Date updatedAt: Date } // Mongoose schema const PostSchema = new Schema( { title: { type: String, required: [true, 'Title is required'], trim: true, maxlength: [200, 'Title cannot exceed 200 characters'], }, content: { type: String, required: [true, 'Content is required'], minlength: [10, 'Content must be at least 10 characters'], }, tags: [ { type: String, trim: true, lowercase: true, }, ], published: { type: Boolean, default: false, }, author: { type: Schema.Types.ObjectId, ref: 'User', required: true, }, }, { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true }, } ) // Indexes for performance PostSchema.index({ author: 1, createdAt: -1 }) PostSchema.index({ published: 1, createdAt: -1 }) PostSchema.index({ tags: 1 }) // Virtual for excerpt PostSchema.virtual('excerpt').get(function () { return this.content.length > 150 ? this.content.substring(0, 150) + '...' : this.content }) export const Post = mongoose.models.Post || mongoose.model('Post', PostSchema) ``` ### Database Connection with Error Handling ```typescript // lib/mongodb.ts (enhanced) import mongoose from 'mongoose' const MONGODB_URI = process.env.MONGODB_URI if (!MONGODB_URI) { throw new Error('Please define the MONGODB_URI environment variable') } interface CachedConnection { conn: typeof mongoose | null promise: Promise | null } // Cache connection in development to avoid connection limit declare global { var mongoose: CachedConnection | undefined } let cached: CachedConnection = global.mongoose || { conn: null, promise: null } if (!global.mongoose) { global.mongoose = cached } export async function connectDB(): Promise { if (cached.conn) { return cached.conn } if (!cached.promise) { const opts = { bufferCommands: false, maxPoolSize: 10, serverSelectionTimeoutMS: 5000, socketTimeoutMS: 45000, family: 4, // Use IPv4, skip trying IPv6 } cached.promise = mongoose.connect(MONGODB_URI!, opts) } try { cached.conn = await cached.promise console.log('MongoDB connected successfully') return cached.conn } catch (error) { cached.promise = null console.error('MongoDB connection error:', error) throw error } } // Graceful shutdown process.on('SIGINT', async () => { if (cached.conn) { await cached.conn.disconnect() console.log('MongoDB disconnected through app termination') process.exit(0) } }) ``` ## Form Handling ### Form with Validation and Error Handling ```typescript // components/CreatePostForm.tsx 'use client'; import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; const CreatePostSchema = z.object({ title: z.string().min(1, 'Title is required').max(200, 'Title too long'), content: z.string().min(10, 'Content must be at least 10 characters'), tags: z.string().optional(), }); type CreatePostForm = z.infer; export function CreatePostForm() { const [isSubmitting, setIsSubmitting] = useState(false); const queryClient = useQueryClient(); const form = useForm({ resolver: zodResolver(CreatePostSchema), defaultValues: { title: '', content: '', tags: '', }, }); const createPostMutation = useMutation({ mutationFn: async (data: CreatePostForm) => { const response = await fetch('/api/posts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ ...data, tags: data.tags?.split(',').map(tag => tag.trim()).filter(Boolean), }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to create post'); } return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['posts'] }); form.reset(); }, }); const onSubmit = async (data: CreatePostForm) => { try { setIsSubmitting(true); await createPostMutation.mutateAsync(data); } catch (error) { console.error('Error creating post:', error); } finally { setIsSubmitting(false); } }; return ( Create New Post
{form.formState.errors.title && (

{form.formState.errors.title.message}

)}