Recipe Guide, Discovery & Management Platform - Next.js, PostgreSQL, Redis, Spoonacular API, AI Search, Analysis, Contentful CMS Full-Stack 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/
- Security: private reports → SECURITY.md · contact@arnobmahmud.com
- Author: Arnob Mahmud · GitHub @arnobt78 · LinkedIn @arnob-mahmud-05839655
- Overview
- Features
- How the app works
- Technology stack
- Keywords (beginner glossary)
- Project structure
- Getting started
- Environment variables
- How to run
- Project walkthrough
- API endpoints
- Pages and routes
- Authentication
- Caching and realtime updates
- AI fallback client
- Database schema
- Components and reusability
- Hooks and data fetching
- Libraries and dependencies
- Testing and quality
- Reusing this project
- Keywords
- Conclusion
- License
- Security
Recipe Guide is an educational and production-ready full-stack app. You can use the live demo with no setup. To run it on your machine you copy .env.example to .env.local and fill in at least the core variables below.
What you will learn by reading and running this repo:
- Next.js 15 App Router — server pages in
app/, interactive UI insrc/ - NextAuth v5 — Google OAuth + email/password (Credentials). Passwords are hashed with bcrypt. There is no Auth0.
- Prisma + PostgreSQL — users, favourites, collections, meal plans, shopping lists, notes, images, videos
- Spoonacular Food API — search, detail, autocomplete, similar recipes, wine pairing
- One catch-all API — almost every REST path lives in
app/api/[...path]/route.tsso Vercel Hobby stays within function limits - TanStack Query — client cache; mutations call
notifyCrud()so lists update without a full page refresh (including other tabs) - Optional extras — Redis, Contentful blog, Cloudinary uploads, Groq/Gemini/OpenRouter/Hugging Face AI, OpenWeather, Resend/Brevo email, Sentry, PostHog, QStash
The home route (/) is a tabbed SPA-style shell: Recipe Search, Favourites, Collections, Meal Plan, Shopping List. Recipe detail is /recipe/[id]. Auth-only tabs appear after login. Logout returns to the search tab without a full reload.
| Feature | What a beginner should know |
|---|---|
| Recipe search | Calls Spoonacular through /api/recipes/search. Filters: cuisine, diet, type, ingredients. Optional extra API_KEY_2… keys rotate when the free daily quota hits 402. |
| Recipe details | /recipe/[id] loads instructions, nutrition, taste, wine pairing, similar recipes. |
| Favourites | Saved in PostgreSQL per user. Heart on a card writes /api/recipes/favourite. |
| Collections | Named lists of recipes with order and color. |
| Meal planning | Week grid: breakfast, lunch, dinner, snack. |
| Shopping list | Built from selected recipes; items stored as JSON on ShoppingList. |
| Notes and ratings | One note per user per recipe. |
| Images / videos | User uploads (Cloudinary) and YouTube-style URLs. Remote photos use SafeImage (Next Image, then native <img> on error). |
| AI | Search, recommendations, analysis, modifications, weather queries. Shared client lib/ai/ tries Groq → Gemini Flash → OpenRouter :free → Hugging Face. Missing keys skip that provider. |
| Blog | Contentful via /api/cms/blog. Empty state if CMS env is unset. |
| Business insights | Auth dashboard; aggregated SQL + Redis 60s cache (lib/business-insights.ts). |
| API status / docs | /api-status and /api-docs for learners. |
| Filter presets | Save search filters (auth). |
| Email share | Resend or Brevo if configured. |
| Realtime | After CRUD, server publishes an event; SSE /api/events/stream invalidates React Query in all open tabs. |
Browser (React 18 + TanStack Query)
│ GET/POST /api/...
▼
app/api/[...path]/route.ts ← one serverless function, path[] routing
│
├── Spoonacular (lib/recipe-api.ts) public recipe data
├── Prisma / PostgreSQL user data
├── lib/ai/completeChat() optional LLM
├── Redis (optional) cache + SSE seq
└── notifyCrud(domain)
→ Redis event
→ SSE to RealtimeProvider
→ invalidateByAppEvent()
→ UI refetch (no full page reload)
Mental model: Spoonacular owns the recipes. Your database owns the user’s relationship to those recipes (favourite, collection, meal slot). The catch-all route is a traffic cop: it reads path from the URL (/api/collections/abc → ["collections", "abc"]) and runs the matching handler.
| Layer | What we use | Why it is here |
|---|---|---|
| Framework | Next.js 15.5.9 (App Router) | SSR pages, API routes, deploy on Vercel |
| UI | React 18.3, Tailwind 3.4, daisyUI, Radix/shadcn, Framer Motion, Lucide | Accessible components + motion |
| Language | TypeScript 5.7 | Types in src/types.ts and Prisma |
| Database | PostgreSQL (Neon or any host) + Prisma 6 | User CRUD; Prisma 7 is not used |
| Auth | NextAuth v5 (auth.ts) |
JWT session; Google + Credentials |
| Recipes | Spoonacular | Search and detail JSON |
| Client cache | TanStack Query v5 | staleTime: Infinity until invalidation |
| Server cache | Upstash Redis (optional) | Search/recipe/insights TTLs + SSE |
| AI | Groq, Gemini, OpenRouter :free, Hugging Face |
See lib/ai/providers.ts |
| Lint / test | ESLint 9 (eslint.config.mjs), Vitest |
npm run lint / npm run test |
| Hosting | Vercel | next build on each push to main |
This is not a Vite SPA. The test runner is Vitest. The app runtime is Next.js.
| Keyword | Short meaning in this repo |
|---|---|
| App Router | Next.js folders under app/ are URLs. app/page.tsx is /. |
| Server Component | Default in app/. Can fetch on the server. No useState. |
| Client Component | File starts with "use client". Needed for clicks, hooks, dialogs. |
| Catch-all route | app/api/[...path]/route.ts handles /api/anything/here. |
| Prisma | ORM: TypeScript talks to PostgreSQL using schema.prisma. |
| NextAuth / Auth.js | Login library. Session cookie + JWT. |
| TanStack Query | Caches API results in the browser; invalidateQueries after mutations. |
| SSE | Server-Sent Events: a one-way stream so other tabs learn about CRUD. |
| Zod | Schema validation on some forms (@hookform/resolvers + react-hook-form). |
| bcrypt | One-way password hash. We never store raw passwords. |
| DSN | Sentry project URL (NEXT_PUBLIC_SENTRY_DSN). |
| staleTime | How long Query treats data as fresh. Infinity = until we invalidate. |
recipe-spoonacular/
├── app/ # Next.js App Router (URLs)
│ ├── page.tsx # Home: search + tabs
│ ├── layout.tsx # Root layout + providers
│ ├── recipe/[id]/page.tsx # Recipe detail (SSR)
│ ├── blog/ blog/[slug]/ # Contentful blog
│ ├── business-insights/ # Auth analytics
│ ├── api-docs/ api-status/ # Learner dashboards
│ ├── test-sentry/ # Sentry smoke page
│ └── api/
│ ├── [...path]/route.ts # Almost all REST + SSE
│ ├── auth/[...nextauth]/ # NextAuth handlers
│ ├── auth/signup-nextauth/ # Email/password signup
│ ├── jobs/scheduled/ # QStash cron
│ └── test/redis/ # Redis probe (not for production)
├── src/
│ ├── components/ # Feature UI (see folders below)
│ ├── components/pages/ # Client page shells
│ ├── components/providers/ # Query, realtime, PostHog
│ ├── components/ui/ # shadcn primitives + SafeImage
│ ├── context/ # AuthContext, RecipeContext
│ ├── hooks/ # React Query hooks
│ ├── utils/ # queryInvalidation, cacheStorage, …
│ ├── api.ts # Browser fetch helpers
│ └── types.ts # Shared TS types
├── lib/ # Server-only (no React)
│ ├── ai/ # Free-tier LLM fallback (REQ-0010)
│ ├── prisma.ts redis.ts # Clients
│ ├── recipe-api.ts # Spoonacular
│ ├── business-insights.ts # Aggregated stats
│ ├── realtime/ # SSE publish / stream
│ └── user-registration.ts # Signup + cache bust
├── prisma/schema.prisma
├── auth.ts # NextAuth config
├── eslint.config.mjs # ESLint 9
├── .env.example # Copy to .env.local
└── SECURITY.md
Rule of thumb: UI in src/. Database, Redis, Spoonacular, AI in lib/. New business APIs go in the catch-all, not a new app/api/foo/route.ts (NextAuth is the exception).
- Node.js 18 or later
- npm
- A PostgreSQL database (free: Neon)
- A Spoonacular API key (free: spoonacular.com/food-api)
You do not need Redis, AI keys, Cloudinary, or Contentful to search recipes and sign up. Those features stay off or show empty/fallback UI until you add keys.
git clone https://github.com/arnobt78/Recipe-Guide-Discovery-Management-Platform--NextJS-Spoonacular-FullStack.git
cd Recipe-Guide-Discovery-Management-Platform--NextJS-Spoonacular-FullStack
npm install
cp .env.example .env.local
# Edit .env.local — at least DATABASE_URL, API_KEY, AUTH_SECRET, AUTH_URL
npm run prisma:generate
npm run prisma:push
npm run devOpen http://localhost:3000.
postinstall already runs prisma generate after npm install. prisma:push creates tables from schema.prisma (this project uses push, not a migration history, for local/dev).
There is a template: .env.example. Copy it:
cp .env.example .env.localNext.js loads .env.local automatically. Never commit .env.local or real secrets.
| Situation | What you need |
|---|---|
| Only using the live demo | Nothing. No env on your laptop. |
| Local search + login + favourites | Required four: DATABASE_URL, API_KEY, AUTH_SECRET, AUTH_URL |
| Google login | Plus GOOGLE_ID and GOOGLE_SECRET |
| Blog, AI, Redis, uploads, email, weather, analytics | Optional keys below; app degrades if they are empty |
API_KEY=your_spoonacular_api_key
DATABASE_URL=postgresql://user:password@host:5432/database?sslmode=require
AUTH_SECRET=paste_output_of_openssl_rand_base64_32
AUTH_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000Leave NEXT_PUBLIC_API_URL empty so the browser calls same-origin /api/... (recommended).
API_KEY_2=
API_KEY_3=
# GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET also work
GOOGLE_ID=
GOOGLE_SECRET=Fallback order is Groq → Gemini → OpenRouter :free → Hugging Face. Model IDs live in lib/ai/providers.ts, not in each route.
GROQ_LLAMA_API_KEY=
GOOGLE_GEMINI_API_KEY=
OPENROUTER_API_KEY=
HUGGING_FACE_INFERENCE_API_KEY=Aliases: GROQ_API_KEY, HF_TOKEN. OpenRouter IDs must stay :free (or openrouter/free).
UPSTASH_REDIS_URL=
UPSTASH_REDIS_TOKEN=
CMS_SPACE_ID=
CMS_DELIVERY_TOKEN=
CMS_ENVIRONMENT=master
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=
OPENWEATHER_API_KEY=
RESEND_TOKEN=
BREVO_API_KEY=
EMAIL_SENDER_ADDRESS=you@example.com
NEXT_PUBLIC_SENTRY_DSN=
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
QSTASH_TOKEN=| Variable | Where to create it |
|---|---|
API_KEY |
Spoonacular → dashboard → API key |
DATABASE_URL |
Neon → connection string; add ?sslmode=require |
AUTH_SECRET |
Terminal: openssl rand -base64 32 |
GOOGLE_ID / GOOGLE_SECRET |
Google Cloud Console → Credentials → OAuth 2.0 Web client. Redirect: http://localhost:3000/api/auth/callback/google (production: https://your-domain/api/auth/callback/google) |
UPSTASH_REDIS_* |
Upstash → Redis → URL + token |
CMS_* |
Contentful → Space → API keys (CDA) |
CLOUDINARY_* |
Cloudinary dashboard |
GROQ_LLAMA_API_KEY |
Groq Console |
GOOGLE_GEMINI_API_KEY |
Google AI Studio |
OPENROUTER_API_KEY |
OpenRouter keys |
HUGGING_FACE_INFERENCE_API_KEY |
Hugging Face |
OPENWEATHER_API_KEY |
OpenWeather |
RESEND_TOKEN / BREVO_API_KEY |
Resend or Brevo |
NEXT_PUBLIC_SENTRY_DSN |
Sentry → project DSN |
NEXT_PUBLIC_POSTHOG_* |
PostHog project API key |
On Vercel: Project → Settings → Environment Variables. Use the same names as .env.example (including GROQ_LLAMA_API_KEY).
| Command | Description |
|---|---|
npm run dev |
Dev server with Turbopack → http://localhost:3000 |
npm run dev:webpack |
Dev server with Webpack |
npm run build |
Production build |
npm run start |
Serve the production build |
npm run lint |
ESLint 9 via next lint |
npm run test |
Vitest (lib/__tests__/) |
npm run prisma:generate |
Generate Prisma Client |
npm run prisma:push |
Sync schema to the database |
npm run prisma:studio |
Visual DB browser |
app/page.tsx renders a client shell (HomePage / AppContent). The hero search bar (SearchInput) hits autocomplete + search. Tabs: Search (default), Favourites, Collections, Meal Plan, Shopping List. Tab query ?tab=favourites is optional; search stays a clean /.
Server page loads Spoonacular information, then RecipePage (client) shows tabs (details, nutrition, taste), similar recipes, AI analysis if keys exist, and auth actions (favourite, collection, meal plan, notes, images, videos, email share).
/api/cms/blog reads Contentful. Without CMS_* you still get the page, usually empty.
Requires login. lib/business-insights.ts runs consolidated counts (not N+1 loops). Redis key business:insights TTL 60s. ?probe=1 is a cheap health check.
Status polls /api/status. Docs list catch-all paths for humans.
Example: favourite a recipe.
- Hook
useIsFavourite→POST /api/recipes/favourite - Route checks
requireAuth(), writes Prisma, thennotifyCrud("favourites") - Redis sequence bumps; SSE clients refetch favourites + insights
- Heart state updates immediately; back-navigation still shows fresh Query cache
Almost everything is app/api/[...path]/route.ts. Example: /api/recipes/search → path = ["recipes", "search"].
Protected routes call requireAuth() in lib/api-utils-nextjs.ts. The browser uses src/api.ts (getApiUrl, session cookies).
| Method | Path | Notes |
|---|---|---|
| GET | /api/recipes/search |
Query: searchTerm, page, filters |
| GET | /api/recipes/autocomplete |
Typeahead |
| GET | /api/recipes/[id]/information |
Full recipe |
| GET | /api/recipes/[id]/summary |
HTML summary |
| GET | /api/recipes/[id]/similar |
Similar recipes |
| GET/POST/DELETE | /api/recipes/favourite |
Auth |
| GET/POST/DELETE | /api/recipes/images |
Auth; query recipeId |
| GET/POST/PUT/DELETE | /api/recipes/notes |
Auth |
| GET/POST/DELETE | /api/recipes/videos |
Auth |
| Method | Path | Notes |
|---|---|---|
| POST | /api/ai/search |
Natural-language search |
| POST | /api/ai/recommendations |
Suggestions |
| POST | /api/ai/analysis |
Nutrition / allergens style analysis |
| POST | /api/ai/modifications |
Dietary conversion, simplify |
All of these call completeChat() in lib/ai/. If no AI keys are set, the route should fail gracefully rather than crash the rest of the app.
| Method | Path |
|---|---|
| GET/POST | /api/collections |
| GET/PUT/DELETE | /api/collections/[id] |
| GET/POST/DELETE | /api/collections/[id]/items |
| GET | /api/collections/[id]/recipes |
| GET/POST/DELETE | /api/meal-plan |
| GET/POST/PUT/DELETE | /api/shopping-list |
| GET/POST/PUT/DELETE | /api/filters/presets |
| Method | Path | Notes |
|---|---|---|
| GET | /api/events/stream |
SSE realtime |
| GET | /api/status |
Health |
| GET | /api/cms/blog |
Contentful list |
| GET | /api/cms/blog/[slug] |
One post |
| GET | /api/business-insights |
Auth stats |
| GET | /api/food/wine/dishes |
Spoonacular |
| GET | /api/food/wine/pairing |
Spoonacular |
| POST | /api/upload |
Cloudinary |
| GET/POST | /api/weather/suggestions |
OpenWeather + AI query gen |
| POST | /api/email/share |
Resend/Brevo |
| Path | Role |
|---|---|
/api/auth/[...nextauth] |
NextAuth (login, callback, session, signout) |
/api/auth/signup-nextauth |
Create Credentials user (lib/user-registration.ts) |
| Route | Rendering | Description |
|---|---|---|
/ |
Dynamic | Search + tabs |
/recipe/[id] |
Dynamic | Recipe detail |
/blog |
Dynamic | Post list |
/blog/[slug] |
Dynamic | Post |
/business-insights |
Dynamic | Auth dashboard |
/api-status |
Dynamic | Endpoint health |
/api-docs |
Dynamic | Human API reference |
/test-sentry |
Static | Error-tracking check |
/robots.txt |
Static | From app/robots.ts |
Configured in auth.ts (NextAuth v5):
- Credentials — email + password. Lookup in Prisma;
bcrypt.compareagainstUser.password. - Google —
GOOGLE_ID/GOOGLE_SECRET. On first login,createLocalUserupserts the user.
Signup UI (RegisterDialog) posts to /api/auth/signup-nextauth. Login UI (LoginDialog) uses signIn("credentials") or signIn("google").
AuthContext exposes user, login, logout to the Navbar and tabs. Session is a JWT; API routes read it with auth() / requireAuth().
Three layers:
- Redis (if Upstash is set) — search ~30m, recipe ~24h, insights 60s
- TanStack Query — in-memory; default
staleTime: Infinityuntil invalidation - sessionStorage / localStorage — search results survive a refresh in the same browser
On successful mutation the catch-all runs:
await notifyCrud("favourites"); // domain: collections | mealPlan | …That publishes Redis app:events:seq and busts insights. RealtimeProvider listens on GET /api/events/stream and calls invalidateByAppEvent() in src/utils/queryInvalidation.ts. Other tabs and the back button then show updated data without a manual refresh.
Without Redis, in-tab invalidation still runs from the mutation hooks; cross-tab SSE is weaker.
lib/ai/ is a small OpenAI-compatible stack:
| File | Job |
|---|---|
providers.ts |
Ordered registry + env key names |
client.ts |
POST {baseUrl}/chat/completions, 12s timeout, never throws |
index.ts |
completeChat() walks providers/models |
parse-json.ts |
Pull JSON out of LLM text |
Tests: lib/__tests__/ai-fallback.test.ts (TC-0024). More detail: docs/LLM_MODEL_SELECTION.md.
Defined in prisma/schema.prisma. Recipe content is not copied into Postgres; we store Spoonacular recipeId plus user metadata.
| Model | Purpose |
|---|---|
User |
id, email, name, picture, optional hashed password |
FavouriteRecipes |
Unique (recipeId, userId) |
RecipeCollection / CollectionItem |
Named lists + order |
RecipeNote |
Content, rating, tags |
MealPlan / MealPlanItem |
Week + meal type |
ShoppingList |
Name, recipe ids, items JSON |
RecipeImage |
Cloudinary URL + type |
RecipeVideo |
External URL |
FilterPreset |
Saved filters JSON |
User delete cascades to related rows.
Copy a folder under src/components/ plus src/types.ts / src/api.ts if you reuse UI elsewhere. Keep Tailwind + the same cn() helper (clsx + tailwind-merge).
import RecipeCard from "@/components/recipes/RecipeCard";
<RecipeCard
recipe={recipe}
isFavourite={false}
onFavouriteButtonClick={(r) => toggleFavourite(r)}
/>;Default click navigates to /recipe/[id]. Pass onClick to override.
import SearchInput from "@/components/search/SearchInput";
<SearchInput
value={term}
onChange={setTerm}
onSubmit={(e) => {
e.preventDefault();
runSearch(term);
}}
placeholder="Search recipes..."
/>;Autocomplete uses useAutocompleteRecipes (debounced).
import EmptyState from "@/components/common/EmptyState";
<EmptyState message="No recipes yet" subtitle="Try another search." />;Use for any remote URL (Spoonacular, Cloudinary, Google avatars). Do not use raw <img> for those hosts — next.config.js images.remotePatterns allowlists domains.
import { SafeImage } from "@/components/ui/safe-image";
<SafeImage src={url} alt={title} width={400} height={300} />;| Folder | Role |
|---|---|
components/ui/ |
Button, Card, Dialog, Tabs — portable shadcn |
components/recipes/ |
Cards, gallery, notes, share |
components/auth/ |
Login / register dialogs |
components/layout/ |
Navbar, Footer, tabs |
components/skeletons/ |
Loading placeholders |
components/pages/ |
Full client pages wired to hooks |
Hooks live in src/hooks/ and wrap TanStack Query + src/api.ts.
import { useSearchRecipes } from "@/hooks/useRecipes";
import { useIsFavourite } from "@/hooks/useIsFavourite";
import { useCollections } from "@/hooks/useCollections";
import { useMealPlan } from "@/hooks/useMealPlan";
import { useShoppingList } from "@/hooks/useShoppingList";
const { searchRecipes, data, isLoading } = useSearchRecipes();
searchRecipes("pasta", 1, { cuisine: "Italian" });
const { isFavourite, toggleFavourite } = useIsFavourite(recipeId);
const { collections, createCollection } = useCollections();
const { addToMealPlan } = useMealPlan();
const { shoppingList } = useShoppingList();Auth-gated hooks use useAuthCheck() so they disable when logged out. After logout, related caches are cleared.
To reuse in another Next app: copy the hook, src/api.ts helpers, and the matching catch-all branch (or a dedicated route with the same JSON shape).
| Package | What it does here |
|---|---|
next / react / react-dom |
App framework and UI |
next-auth |
Login session |
@prisma/client / prisma |
Database |
@tanstack/react-query |
Client cache and mutations |
@upstash/redis |
Optional server cache + SSE seq |
bcryptjs |
Password hashing |
zod + react-hook-form |
Form validation |
cloudinary |
Image upload API |
@sentry/nextjs |
Errors (v10.x) |
posthog-js |
Product analytics |
framer-motion |
Animations |
lucide-react / react-icons |
Icons |
sonner |
Toasts |
vitest |
Unit tests |
eslint 9 + eslint-config-next 15.5.9 |
Lint |
Install the same majors if you extract a feature. Do not jump to Next 16 or Prisma 7 without a dedicated upgrade; this repo is pinned to Next 15.5.9 and Prisma 6.
npm run test # Vitest — business-insights, realtime, AI fallback
npm run lint # ESLint 9
npm run build # next buildAutomated cases include TC-0020 (insights query count), TC-0021 (realtime payload), TC-0024 (AI fallback), TC-0025 (tooling/lint/build). There is no full E2E suite yet.
| You want | Copy / follow |
|---|---|
| Tabbed recipe search UI | src/components/search, recipes, hooks/useRecipes.ts |
| Auth + user tables | auth.ts, lib/user-registration.ts, Prisma User |
| One Vercel function for many APIs | Catch-all path[] pattern |
| CRUD that updates every tab | notifyCrud + RealtimeProvider + queryInvalidation.ts |
| Free LLM calls | lib/ai/ as a drop-in |
| Remote images that survive optimizer 402s | SafeImage |
Keep env names, requireAuth() on writes, and never commit .env.local.
recipe app, Next.js 15, App Router, React 18, TypeScript, Spoonacular API, PostgreSQL, Prisma, NextAuth v5, TanStack Query, Redis, SSE, full-stack, meal planning, shopping list, collections, favourites, AI fallback, Groq, Gemini, OpenRouter, Hugging Face, Contentful, Cloudinary, Tailwind, shadcn, Vitest, ESLint 9, Vercel, educational project
Recipe Guide is a complete Next.js 15 recipe platform you can run, fork, and teach from:
- App Router + a single catch-all API for Hobby-plan limits
- Prisma + PostgreSQL for user data; Spoonacular for recipe content
- NextAuth v5 (Google + Credentials, bcrypt)
- React Query + SSE so CRUD shows up immediately without a refresh
- Optional AI, blog, Redis, uploads, and monitoring — off until you add keys
Use it as a learning map, a starter, or a reference for catch-all routing, cache invalidation, and free-tier LLM fallback.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
Private vulnerability reports: SECURITY.md → contact@arnobmahmud.com. Please do not file public issues for security bugs.
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! 😊




















