Recipe Guide, Discovery & Management Platform - Next.js, PostgreSQL, Redis, Spoonacular API, Contentful CMS FullStack Project
A modern full-stack recipe discovery and management platform built with Next.js 15, React 18, TypeScript, PostgreSQL, and the Spoonacular API. Search, save, and manage recipes with favourites, collections, meal planning, shopping lists, AI-powered analysis, blog (Contentful), and business insights. Built for learning and production use.
- Live Demo: https://recipe-smart.vercel.app/
- Overview
- Features
- Technology Stack
- Project Structure
- Getting Started
- Environment Variables
- How to Run
- Project Walkthrough
- API Endpoints
- Routes
- Database Schema
- Components & Reusability
- Hooks & Data Fetching
- Keywords
- Conclusion
Recipe Guide is an educational and production-ready full-stack application that demonstrates:
- Next.js 15 App Router with server and client components
- NextAuth v5 for authentication (Google OAuth + email/password via Credentials)
- Prisma + PostgreSQL (e.g. NeonDB) for user data, favourites, collections, meal plans, shopping lists, notes, images, and videos
- Spoonacular API for recipe search, details, autocomplete, and similar recipes
- Unified API handler at
/api/[...path]that routes requests by path (recipes, collections, meal-plan, shopping-list, CMS, AI, weather, etc.) - Optional integrations: Contentful (blog), Cloudinary (image uploads), Upstash Redis (caching), OpenRouter/Gemini/Groq/Hugging Face (AI), OpenWeather (weather-based suggestions), Resend/Brevo (email sharing), Sentry (errors), PostHog (analytics), QStash (scheduled jobs)
The app uses a single-page experience on the home route (/) with tab-based navigation: Recipe Search, Favourites, Collections, Meal Plan, and Shopping List. Recipe detail is a separate dynamic route /recipe/[id]. All auth-protected features require a logged-in user; logout clears session and redirects to the search tab without a full page refresh.
| Feature | Description |
|---|---|
| Recipe Search | Advanced search with filters (cuisine, diet, type, ingredients). Spoonacular API with optional API key rotation. |
| Recipe Details | Full recipe info: instructions, ingredients, nutrition, taste, wine pairing, similar recipes. |
| Favourites | Save and manage favourite recipes (auth required). |
| Collections | Create custom recipe collections with custom ordering (auth required). |
| Meal Planning | Weekly meal planner with breakfast, lunch, dinner, snack slots (auth required). |
| Shopping List | Auto-generated shopping lists from selected recipes (auth required). |
| Recipe Notes | Personal notes and ratings per recipe (auth required). |
| Recipe Images | User-uploaded images per recipe (Cloudinary, auth required). |
| Recipe Videos | User-added videos (YouTube, etc.) per recipe (auth required). |
| AI Features | Recipe analysis, recommendations, modifications, weather-based suggestions (optional API keys). |
| Blog | Contentful CMSβpowered blog list and post pages. |
| Business Insights | Platform statistics, AI predictions, trends (auth required). |
| API Status | Real-time API endpoint health monitoring. |
| API Docs | Endpoint documentation grouped by category. |
| Filter Presets | Save and load search filter presets (auth required). |
| Email Sharing | Share recipes via email (Resend or Brevo). |
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router, Turbopack optional) |
| UI | React 18, Tailwind CSS, ShadCN UI (Radix), Framer Motion, Lucide icons |
| Language | TypeScript 5.7 |
| Database | PostgreSQL (e.g. NeonDB), Prisma ORM |
| Auth | NextAuth v5 (JWT, Google OAuth, Credentials provider) |
| Recipe API | Spoonacular Food API |
| Caching | Upstash Redis (optional) |
| CMS | Contentful |
| Image Upload | Cloudinary |
| Monitoring | Sentry, PostHog |
| Hosting | Vercel |
recipe-spoonacular/
βββ app/ # Next.js App Router
β βββ api/
β β βββ [...path]/route.ts # Unified API handler (recipes, collections, meal-plan, etc.)
β β βββ auth/
β β β βββ [...nextauth]/ # NextAuth v5
β β β βββ login/ # Credentials login
β β β βββ signup/ # Credentials signup
β β β βββ signup-nextauth/ # NextAuth signup
β β βββ jobs/scheduled/ # QStash cron
β β βββ test/redis/ # Redis test
β βββ api-docs/page.tsx # API documentation UI
β βββ api-status/page.tsx # API status dashboard
β βββ blog/
β β βββ page.tsx # Blog list
β β βββ [slug]/page.tsx # Blog post
β βββ business-insights/page.tsx
β βββ recipe/[id]/page.tsx # Recipe detail page
β βββ test-sentry/page.tsx # Sentry test
β βββ layout.tsx # Root layout, metadata, providers
β βββ page.tsx # Home (search + tabs)
βββ src/
β βββ components/
β β βββ analysis/ # AI recipe analysis, nutrition, recommendations
β β βββ auth/ # LoginDialog, RegisterDialog
β β βββ blog/ # BlogPostCard, BlogPostList, BlogPostDetail
β β βββ collections/ # CollectionManager, CollectionCard, AddToCollectionDialog
β β βββ common/ # EmptyState, ErrorBoundary, ConfirmationDialog
β β βββ filters/ # AdvancedFilters, FilterPresets
β β βββ hero/ # HeroSearchSection
β β βββ insights/ # BusinessInsightsDashboard
β β βββ layout/ # Navbar, Footer, TabNavigation, AppContent
β β βββ meal-planning/ # MealPlanner
β β βββ pages/ # HomePage, RecipePage, BlogPage, ApiDocsPage, etc.
β β βββ recipes/ # RecipeCard, RecipeDetailCard, RecipeImageGallery, RecipeNotes
β β βββ search/ # SearchInput, SearchResultsMetadata
β β βββ shopping/ # ShoppingListGenerator, ImageUploader
β β βββ skeletons/ # Loading skeletons for each section
β β βββ status/ # ApiStatusDashboard
β β βββ ui/ # ShadCN primitives (Button, Card, Dialog, Tabs, etc.)
β β βββ videos/ # RecipeVideoPlayer
β β βββ weather/ # WeatherBasedSuggestions, WeatherWidget
β βββ config/upload-presets/ # Cloudinary upload presets
β βββ context/ # AuthContext, RecipeContext
β βββ hooks/ # useRecipes, useCollections, useMealPlan, useRecipeImages, etc.
β βββ lib/ # posthog, utils
β βββ utils/ # queryInvalidation, recipeScaling, imageUtils, etc.
β βββ api.ts # Client-side API helpers (getApiUrl, getAuthHeaders, fetch wrappers)
β βββ types.ts # Shared TypeScript types (Recipe, RecipeImage, TabType, etc.)
β βββ global.css
βββ lib/ # Server-side utilities
β βββ api-key-tracker.ts # Spoonacular API key rotation (API_KEY, API_KEY_2, ...)
β βββ api-utils-nextjs.ts # CORS, auth (JWT/session) for API routes
β βββ prisma.ts # Prisma client singleton
β βββ recipe-api.ts # Spoonacular API calls
β βββ redis-cache.ts # Redis caching helpers
β βββ redis.ts # Upstash Redis client
β βββ qstash.ts # QStash client for cron
βββ prisma/
β βββ schema.prisma # Database schema (User, FavouriteRecipes, RecipeCollection, etc.)
βββ public/ # Static assets (images, icons)
βββ auth.ts # NextAuth v5 config (Google, Credentials)
βββ instrumentation.ts # Sentry instrumentation
βββ next.config.js # Next config (images remotePatterns, etc.)
βββ .env.example # Example environment variables
βββ README.md # This file
- Node.js 18 or later
- npm or pnpm
- PostgreSQL database (e.g. NeonDB)
- Spoonacular API key (free tier: spoonacular.com/food-api)
# Clone the repository
git clone https://github.com/arnobt78/recipe-spoonacular.git
cd recipe-spoonacular
# Install dependencies
npm install
# Copy environment file and fill in required values
cp .env.example .env.local
# Generate Prisma client
npm run prisma:generate
# Push schema to database (creates tables)
npm run prisma:push
# Start development server
npm run devOpen http://localhost:3000.
Create a .env.local file in the project root. Use .env.example as a template. Next.js loads .env.local automatically; never commit it.
# Spoonacular API (required for recipe search and details)
API_KEY=your_spoonacular_api_key_here
# Optional: add more keys for rotation (reduces 402 errors on free tier)
# API_KEY_2=optional_second_key
# API_KEY_3=optional_third_key
# PostgreSQL connection string (required for auth and user data)
DATABASE_URL=postgresql://user:password@host/database?sslmode=require# Required for NextAuth
AUTH_SECRET=your-secret-here
# Generate: openssl rand -base64 32
# App URL (used for callbacks and links)
AUTH_URL=http://localhost:3000
# Production: https://your-domain.com
# Google OAuth (optional; without these, only Credentials login works)
GOOGLE_ID=your-google-client-id
GOOGLE_SECRET=your-google-client-secret
# Alternative names also supported: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET# Redis (Upstash) β caching
UPSTASH_REDIS_URL=https://xxx.upstash.io
UPSTASH_REDIS_TOKEN=your-token
# Contentful β blog
CMS_SPACE_ID=your-space-id
CMS_DELIVERY_TOKEN=your-delivery-token
CMS_ENVIRONMENT=master
# Cloudinary β recipe image uploads
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
# AI (at least one for analysis/recommendations/modifications)
OPENROUTER_API_KEY=your-key
GOOGLE_GEMINI_API_KEY=your-key
GROQ_LLAMA_API_KEY=your-key
HUGGING_FACE_INFERENCE_API_KEY=your-key
# Weather β weather-based recipe suggestions
OPENWEATHER_API_KEY=your-key
# Email β recipe sharing (Resend or Brevo)
RESEND_TOKEN=your-token
BREVO_API_KEY=your-key
EMAIL_SENDER_ADDRESS=noreply@yourdomain.com
# Sentry β error tracking
SENTRY_DSN=your-sentry-dsn
NEXT_PUBLIC_SENTRY_DSN=your-sentry-dsn
# PostHog β analytics
NEXT_PUBLIC_POSTHOG_KEY=your-key
NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com
# App URLs (metadata, links in emails)
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:3000| Variable | How to get it |
|---|---|
API_KEY |
Spoonacular β Sign up β Dashboard β API Key |
DATABASE_URL |
Neon or any PostgreSQL host β Connection string (include ?sslmode=require for Neon) |
AUTH_SECRET |
Run openssl rand -base64 32 in terminal |
GOOGLE_ID / GOOGLE_SECRET |
Google Cloud Console β APIs & Services β Credentials β Create OAuth 2.0 Client ID (Web app), set redirect URI to http://localhost:3000/api/auth/callback/google |
UPSTASH_REDIS_* |
Upstash β Create Redis database β Copy URL and token |
CMS_* |
Contentful β Space Settings β API keys β Content delivery API token; Environment = master |
CLOUDINARY_* |
Cloudinary β Dashboard β Account details |
OPENROUTER_API_KEY |
OpenRouter β Keys |
GOOGLE_GEMINI_API_KEY |
Google AI Studio β Get API key |
OPENWEATHER_API_KEY |
OpenWeather β Sign up β API key |
RESEND_TOKEN |
Resend β API Keys |
BREVO_API_KEY |
Brevo β SMTP & API β API Keys |
SENTRY_DSN |
Sentry β Create project β Client keys (DSN) |
NEXT_PUBLIC_POSTHOG_* |
PostHog β Project Settings β Project API key and host |
| Command | Description |
|---|---|
npm run dev |
Start dev server (Turbopack) at http://localhost:3000 |
npm run dev:webpack |
Start dev server with Webpack |
npm run build |
Production build |
npm run start |
Run production server |
npm run lint |
Run ESLint |
npm run prisma:generate |
Generate Prisma client |
npm run prisma:push |
Push Prisma schema to database (no migrations) |
npm run prisma:studio |
Open Prisma Studio (DB GUI) |
- Hero: Search bar and optional advanced filters (cuisine, diet, type, etc.).
- Tabs: Recipe Search (default), Favourites, Collections, Meal Plan, Shopping List (auth-only tabs appear when logged in).
- Search: Requests go to
/api/recipes/search(Spoonacular). Results show as cards with image, title, badges (vegan, gluten-free, etc.), scores, and favourite button. - URL: Tab state is synced to query param
?tab=favourites(etc.);?tab=searchis omitted for a clean/.
- Data: Fetched via
/api/recipes/[id]/information(and related endpoints). Rendered byRecipePageandRecipeDetailCard. - Tabs: Details (instructions, ingredients), Summary, Info (meta), Nutrition, Taste.
- Actions: Add to favourites, add to collection, add to meal plan, add to shopping list, share by email.
- User content (when authenticated): Notes, recipe images (upload via Cloudinary), recipe videos.
- List: From Contentful via
/api/cms/blog. Each post links to/blog/[slug]. - Post: Fetched via
/api/cms/blog/[slug]. Rendered withBlogPostDetail.
- Auth: Required. Shows platform stats, popular recipes, AI-related metrics. Data from
/api/business-insights.
- Dashboard: Status of main API endpoints; auto-refresh (e.g. every 10 seconds).
- Reference: Endpoints grouped by category (recipes, collections, meal-plan, etc.) with method, path, and parameters.
The app uses a unified API handler at app/api/[...path]/route.ts. The path array is derived from the URL: e.g. /api/recipes/search β path = ["recipes", "search"]. GET, POST, PUT, DELETE are handled in the same file.
| Method | Path | Description |
|---|---|---|
| GET | /api/recipes/search |
Search recipes (query: searchTerm, page, filters). Proxies Spoonacular. |
| GET | /api/recipes/autocomplete |
Autocomplete suggestions. |
| GET | /api/recipes/favourite |
Current user's favourites (auth). |
| POST | /api/recipes/favourite |
Add favourite (auth). |
| DELETE | /api/recipes/favourite |
Remove favourite (auth). |
| GET | /api/recipes/images |
Recipe images for a recipe (auth). Query: recipeId. |
| POST | /api/recipes/images |
Add recipe image (auth). Body: recipeId, imageUrl, imageType. |
| DELETE | /api/recipes/images |
Delete recipe image (auth). |
| GET | /api/recipes/notes |
Recipe note (auth). Query: recipeId. |
| POST/PUT/DELETE | /api/recipes/notes |
Create/update/delete note (auth). |
| GET | /api/recipes/videos |
Recipe videos (auth). Query: recipeId. |
| POST | /api/recipes/videos |
Add video (auth). |
| DELETE | /api/recipes/videos |
Delete video (auth). |
Spoonacular-backed proxy routes (e.g. recipe information, summary, similar) are typically called from the server-side recipe page or via the same handler with path like recipes/[id]/information (implementation may use internal fetch to Spoonacular).
| Method | Path | Description |
|---|---|---|
| POST | /api/ai/search |
AI-powered search. |
| POST | /api/ai/recommendations |
Recipe recommendations. |
| POST | /api/ai/analysis |
Recipe analysis. |
| POST | /api/ai/modifications |
Recipe modifications. |
Require at least one of: OPENROUTER_API_KEY, GOOGLE_GEMINI_API_KEY, GROQ_LLAMA_API_KEY, HUGGING_FACE_INFERENCE_API_KEY.
| Method | Path | Description |
|---|---|---|
| GET | /api/collections |
List collections (auth). |
| GET | /api/collections/[id] |
Get collection (auth). |
| GET | /api/collections/[id]/items |
Collection items (auth). |
| POST | /api/collections |
Create collection (auth). |
| POST | /api/collections/[id]/items |
Add item (auth). |
| PUT | /api/collections/[id] |
Update collection (auth). |
| DELETE | /api/collections/[id] |
Delete collection (auth). |
| DELETE | /api/collections/[id]/items |
Remove item (auth). |
| Method | Path | Description |
|---|---|---|
| GET | /api/meal-plan |
Get meal plan (auth). Query: e.g. weekStart. |
| POST | /api/meal-plan |
Add/update meal plan (auth). |
| DELETE | /api/meal-plan |
Clear meal plan (auth). |
| GET | /api/shopping-list |
Get shopping list (auth). |
| POST | /api/shopping-list |
Create/update list (auth). |
| PUT | /api/shopping-list |
Update list (auth). |
| DELETE | /api/shopping-list |
Delete list (auth). |
| Method | Path | Description |
|---|---|---|
| GET | /api/food/wine/dishes |
Dishes for a wine. |
| GET | /api/food/wine/pairing |
Wine pairing for a dish. |
| Method | Path | Description |
|---|---|---|
| GET | /api/cms/blog |
List blog posts (Contentful). |
| GET | /api/cms/blog/[slug] |
Single blog post (Contentful). |
| GET | /api/business-insights |
Platform statistics (auth). |
| GET | /api/status |
API health status. |
| Method | Path | Description |
|---|---|---|
| POST | /api/upload |
Image upload to Cloudinary. |
| POST | /api/weather/suggestions |
Weather-based recipe suggestions (OpenWeather). |
| POST | /api/email/share |
Share recipe by email (Resend/Brevo). |
| GET/POST/PUT/DELETE | /api/filters/presets |
Filter presets (auth). |
Auth is enforced via session/JWT in lib/api-utils-nextjs.ts (e.g. requireAuth(request)). Client-side calls use src/api.ts (getApiUrl, getAuthHeaders) so the same routes are hit with credentials.
| Route | Type | Description |
|---|---|---|
/ |
Static | Home: search + tabbed UI (search, favourites, collections, meal plan, shopping). |
/recipe/[id] |
Dynamic | Recipe detail page. |
/blog |
Static | Blog list (Contentful). |
/blog/[slug] |
Dynamic | Single blog post. |
/business-insights |
Static | Analytics dashboard (auth). |
/api-status |
Static | API status dashboard. |
/api-docs |
Static | API documentation. |
/test-sentry |
Static | Sentry test page. |
Key Prisma models (see prisma/schema.prisma):
| Model | Purpose |
|---|---|
| User | Auth user (id, email, name, picture, optional password). Relations: favourites, collections, notes, mealPlans, shoppingLists, recipeImages, filterPresets, recipeVideos. |
| FavouriteRecipes | User favourites (userId, recipeId). Unique per user+recipe. |
| RecipeCollection | User-created collection (name, description, color). |
| CollectionItem | Item in a collection (recipeId, recipeTitle, recipeImage, order). |
| RecipeNote | User note per recipe (content, rating, tags). |
| MealPlan | Weekly plan (weekStart). |
| MealPlanItem | Meal slot (recipeId, dayOfWeek, mealType, servings). |
| ShoppingList | List (name, recipeIds, items JSON). |
| RecipeImage | User-uploaded image (imageUrl, imageType, caption). |
| FilterPreset | Saved search filters (name, filters JSON). |
| RecipeVideo | User-added video (e.g. YouTube URL). |
All user-scoped tables use userId and cascade on user delete.
Components are structured so they can be reused in this app or copied into others. They rely on src/types.ts, src/api.ts, and optionally context/hooks.
RecipeCard β display a recipe with image, title, badges, favourite button:
import RecipeCard from "@/components/recipes/RecipeCard";
<RecipeCard
recipe={recipe}
onFavouriteButtonClick={() => {}}
isFavourite={false}
onRecipeClick={(id) => router.push(`/recipe/${id}`)}
/>;SearchInput β search bar with optional autocomplete:
import SearchInput from "@/components/search/SearchInput";
<SearchInput
value={searchTerm}
onChange={setSearchTerm}
onSubmit={(term) => doSearch(term)}
placeholder="Search recipes..."
/>;EmptyState β empty list message:
import EmptyState from "@/components/common/EmptyState";
<EmptyState
message="No recipes yet"
subtitle="Try searching or add from favourites."
/>;- Pages (
src/components/pages/): Full-page client components (HomePage, RecipePage, BlogPage, ApiDocsPage, BusinessInsightsPage, ApiStatusPage). - Feature components (
src/components/recipes/,collections/,meal-planning/, etc.): Feature-specific UI; often use hooks fromsrc/hooks/. - UI (
src/components/ui/): ShadCN primitives (Button, Card, Dialog, Tabs, Input, etc.). Reusable in any project that uses Tailwind + Radix. - Skeletons (
src/components/skeletons/): Loading placeholders aligned with main components.
Data fetching uses TanStack Query (React Query) with hooks in src/hooks/. The client calls src/api.ts, which uses NEXT_PUBLIC_API_URL and auth headers (session/JWT).
Example: search and favourites
import { useSearchRecipes } from "@/hooks/useRecipes";
import { useIsFavourite } from "@/hooks/useIsFavourite";
const { data, isLoading, searchRecipes } = useSearchRecipes();
const { isFavourite, toggleFavourite } = useIsFavourite(recipeId);
// Search
searchRecipes("pasta", 1, { cuisine: "Italian" });
// Favourite
await toggleFavourite();Example: collections
import { useCollections, useCollectionDetail } from "@/hooks/useCollections";
const { collections, createCollection, isLoading } = useCollections();
const { collection, items } = useCollectionDetail(collectionId);Example: meal plan and shopping list
import { useMealPlan } from "@/hooks/useMealPlan";
import { useShoppingList } from "@/hooks/useShoppingList";
const { mealPlan, addToMealPlan, removeFromMealPlan } = useMealPlan();
const { shoppingList, addRecipes, clearList } = useShoppingList();Example: recipe images and notes
import { useRecipeImages, useAddRecipeImage } from "@/hooks/useRecipeImages";
import { useRecipeNotes } from "@/hooks/useRecipeNotes";
const { data: images } = useRecipeImages(recipeId, true);
const addImage = useAddRecipeImage();
const { note, saveNote } = useRecipeNotes(recipeId);All auth-protected hooks depend on useAuthCheck() or session; when the user logs out, cache is cleared and these queries are disabled or refetched as needed.
recipe app, Next.js, React, TypeScript, Spoonacular API, PostgreSQL, Prisma, NextAuth, full-stack, meal planning, shopping list, recipe collections, favourites, AI recipe, Contentful, Cloudinary, Tailwind CSS, ShadCN, Vercel, Redis, Sentry, recipe search, recipe management, educational project
Recipe Guide is a full-featured recipe platform that demonstrates:
- Next.js 15 App Router with server and client components and a unified API route handler.
- Prisma + PostgreSQL for user data, favourites, collections, meal plans, shopping lists, notes, images, and videos.
- NextAuth v5 for Google OAuth and Credentials (email/password) with session/JWT.
- Spoonacular API for recipe data, with optional multi-key rotation.
- ShadCN UI + Tailwind for a consistent, accessible UI.
- React Query for client-side caching and auth-aware data fetching.
- Optional integrations (Contentful, Cloudinary, Redis, AI, weather, email, Sentry, PostHog) for blog, uploads, caching, AI features, and monitoring.
You can use it as a reference for building similar apps, as a base to extend with new features, or as teaching material for full-stack Next.js and TypeScript.
This is an open-source project - feel free to use, enhance, and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com/.
Enjoy building and learning! π
Thank you! π




















