Template Bakery

A production-ready, full-stack bakery & restaurant management system built with Next.js 16, React 19, MongoDB, and Cloudinary. Includes a polished public website and a powerful admin dashboard.

📅 Version 1.0.0 📦 Next.js 16 + React 19 🗃️ MongoDB + Mongoose ☁️ Cloudinary

Overview

Template Bakery is a complete, full-stack web application built specifically for bakeries, restaurants, and food businesses. It ships with everything you need out of the box:

Features

Public Website

Multiple Homepages

Two distinct homepage layout variants to choose from, with dynamic sections.

Product / Menu Showcase

Dynamic menu with category and sub-category filtering, popular items, and featured sections.

Outlet Locations

Multi-branch listing with business hours, phone, and Google Maps links.

Full Blog System

Blog categories, related posts, infinite scroll, tags, and featured article flags.

Newsletter

Built-in newsletter subscription form with subscriber management in the admin.

Testimonials / Reviews

Customer testimonials with ratings, drag-and-drop ordering, and status control.

SEO Ready

Configurable metadata, Open Graph image, keywords, and page titles from the admin panel.

Dark Mode

Full dark mode support powered by next-themes, with smooth transitions.

Admin Dashboard

Secure Authentication

JWT-based admin login with bcrypt password hashing and 30-day session tokens.

Rich Text Editor

Blog content powered by SunEditor — a full-featured WYSIWYG HTML editor.

Drag-and-Drop Ordering

Reorder banners, categories, testimonials, and more with drag-and-drop (dnd-kit).

Image Upload

Direct Cloudinary uploads from every form — no manual image management needed.

Settings Panel

Configure general info, social links, SEO metadata, page banners, Cloudinary credentials, and T&C from one place.

Dashboard Statistics

At-a-glance stats: total products, blogs, outlets, and subscribers.

Technology Stack

Next.js 16App Router, Server Components, Server Actions
React 19Latest stable release
TypeScript 5Strict type safety throughout
MongoDB 6+via Mongoose 8 ODM
NextAuth v5Session + JWT auth
Tailwind CSS v4Utility-first styling
Radix UI + ShadcnAccessible UI primitives
Framer MotionSmooth animations
React Hook FormPerformant forms
ZodSchema-first validation
TanStack TableHeadless data tables
dnd-kitDrag-and-drop sorting
SunEditorRich text editor
CloudinaryMedia storage & delivery
ZustandLightweight state management
Embla CarouselSmooth carousels
FlatpickrDate & time picker
Lucide IconsModern SVG icon set
React Hot ToastToast notifications
pnpmFast, disk-efficient package manager

Requirements

RequirementMinimum VersionNotes
Node.js20.x or laterLTS recommended
pnpm8.x or laternpm install -g pnpm
MongoDB6.0 or laterLocal or MongoDB Atlas (free tier)
Cloudinary AccountAnyFree tier is sufficient

Installation & Setup

Step 1 — Extract Files

unzip template-bakery.zip -d template-bakery
cd template-bakery

Step 2 — Install Dependencies

# Install pnpm globally (if not already installed)
npm install -g pnpm

# Install all project dependencies
pnpm install

Step 3 — Configure Environment Variables

cp .env.example .env.local
# Then edit .env.local with your actual values

See the Environment Variables section for all required values.

Step 4 — Seed the Database (Optional)

pnpm seed

This creates the initial admin user and populates default settings.

Step 5 — Start Development Server

pnpm dev

Open http://localhost:3000 in your browser. The admin dashboard is at http://localhost:3000/admin/login. The interactive documentation is at http://localhost:3000/documentation.

Environment Variables

Create a .env.local file in the project root with the following variables:

# ─── Database ──────────────────────────────────────────────────
# MongoDB connection string (required)
MONGODB_URI=mongodb+srv://<user>:<password>@cluster.mongodb.net/template-bakery

# ─── Authentication ────────────────────────────────────────────
# Secret key for signing NextAuth JWT tokens (required)
# Generate: openssl rand -base64 32
NEXTAUTH_SECRET=your_strong_random_secret

# ─── Application URLs ──────────────────────────────────────────
# Base URL for internal API calls (server-side fetches)
NEXT_PUBLIC_BASE_URL=http://localhost:3000

# Public application URL (for production absolute links)
NEXT_PUBLIC_APP_URL=https://your-domain.com

# ─── Cloudinary ────────────────────────────────────────────────
# Default upload folder name
CLOUDINARY_FOLDER=uploads

# Cloudinary secure base URL
CLOUDINARY_SECURE_URL_BASE=https://res.cloudinary.com/your-cloud-name
ℹ️ Cloudinary Credentials The cloud_name, api_key, and api_secret are stored and managed through Admin Dashboard → Settings → Cloudinary (saved in the database). Only the folder name and base URL need to be set as environment variables.
⚠️ Security Notice Never commit .env.local to version control. Add it to .gitignore.

Running the Application

CommandDescription
pnpm devStart development server with Turbopack at localhost:3000
pnpm buildCreate an optimized production build
pnpm startServe the production build
pnpm lintRun ESLint code quality checks

Project Structure

template-bakery/ ├── app/ # Next.js App Router root │ ├── (public)/ # Public-facing pages (no auth) │ │ ├── page.tsx # Homepage — Layout 1 │ │ ├── home2/ # Homepage — Layout 2 │ │ ├── menu/ # Products / Menu page │ │ ├── outlets/ # Outlet locations │ │ ├── all-blogs/ # Blog listing │ │ └── blog-details/[slug]/ # Individual blog post │ │ │ ├── (private)/ # Admin dashboard (auth-protected) │ │ └── admin/dashboard/ │ │ ├── page.tsx # Dashboard home │ │ ├── banner/ # Banner management │ │ ├── blogs/ # Blog CRUD │ │ ├── categories/ # Product category CRUD │ │ ├── products/ # Product management │ │ ├── outlets/ # Outlet management │ │ ├── testimonial/ # Testimonial management │ │ └── settings/ # Site settings │ │ │ ├── api/ # REST API routes │ │ ├── blog/ # Public blog endpoints │ │ ├── product/ # Public product endpoints │ │ ├── outlet/ # Public outlet endpoints │ │ └── admin/ # All protected admin APIs │ │ ├── auth/ # Admin auth APIs │ │ ├── banner/ # Banner CRUD │ │ ├── blog/ # Blog CRUD │ │ ├── settings/ # Settings CRUD │ │ └── file/ # File upload endpoint │ │ │ ├── globals.css # Global styles │ └── layout.tsx # Root layout │ ├── actions/ # Next.js Server Actions (by feature) ├── components/ # React components │ ├── custom/ # Custom reusable components │ ├── features/ # Feature-specific components │ ├── ui/ # Shadcn UI base components │ ├── hooks/ # Component-scoped React hooks │ └── providers/ # Context providers │ ├── config/ # App-level configuration │ ├── database.ts # MongoDB connection with caching │ └── cloudinary.ts # Cloudinary client initialization │ ├── lib/ # Server utilities │ ├── async-handler.ts # API route wrapper (auth + validation) │ ├── mongo-adapter.ts # MongoDB aggregation pipeline builder │ ├── server-utils.ts # Shared server utilities │ ├── authenticate.ts # JWT token verification │ └── validation-schema.ts # All Zod validation schemas │ ├── model/ # Mongoose data models │ ├── User.ts │ ├── Blog.ts │ ├── BlogCategory.ts │ ├── Category.ts │ ├── Product.ts │ ├── Outlet.ts │ ├── Banner.ts │ ├── Testimonial.ts │ ├── Subscribe.ts │ ├── HomeSection.ts │ └── Settings.ts │ ├── store/ # Zustand state stores ├── hooks/ # Global React hooks ├── provider/ # Auth & dashboard providers ├── types/ # Global TypeScript types ├── public/ # Static assets ├── next.config.ts # Next.js configuration ├── tailwind.config.ts # Tailwind configuration ├── tsconfig.json # TypeScript configuration └── package.json

Pages & Routes

Public Pages

RouteDescription
/Homepage — Layout 1 with hero, featured products, testimonials
/home2Homepage — Layout 2 (alternate design)
/menuFull product/menu listing with category filter
/outletsAll outlet/branch locations with business hours
/all-blogsBlog listing with category filter and infinite scroll
/blog-listAlternate blog list view
/blog-details/[slug]Individual blog post page
/admin/loginAdmin login page
/documentationBuilt-in interactive documentation

Admin Dashboard Pages (Protected)

All admin routes are under /admin/dashboard/ and require a valid authentication session.

RouteDescription
/admin/dashboardOverview statistics
/admin/dashboard/bannerManage hero banners with drag-and-drop order
/admin/dashboard/blogsBlog list (paginated)
/admin/dashboard/blogs/createCreate new blog post with rich text editor
/admin/dashboard/blogs/edit/[id]Edit existing blog post
/admin/dashboard/blog-categoryManage blog categories
/admin/dashboard/categoriesProduct category list
/admin/dashboard/categories/createCreate product category
/admin/dashboard/categories/edit/[slug]Edit product category
/admin/dashboard/productsProduct/menu item management
/admin/dashboard/outletsOutlet list with reorder
/admin/dashboard/outlets/createCreate new outlet
/admin/dashboard/outlets/edit/[id]Edit outlet details
/admin/dashboard/testimonialCustomer testimonials
/admin/dashboard/subscribeNewsletter subscriber list
/admin/dashboard/client-showcaseClient showcase section
/admin/dashboard/video-showcaseVideo showcase section
/admin/dashboard/settingsAll site settings

API Reference — Public Endpoints

All API responses follow a consistent envelope format:

// Success
{ "success": true, "statusCode": 200, "message": "...", "data": { ... } }

// Error
{ "success": false, "statusCode": 400, "message": "...", "errors": [ ... ] }

Blogs

GET /api/blog

Returns all published blog posts with pagination.

Query Parameters: page (default: 1), limit (default: 10)

GET /api/blog/[slug]

Returns a single blog post by its unique slug.

POST /api/blog/filter

Filters and searches blog posts.

{ "category": "recipes", "search": "chocolate", "page": 1, "limit": 10 }
GET /api/blog/related/[slug]

Returns related blog posts for the given slug.

GET /api/blog-category

Returns all active blog categories.

Products & Categories

GET /api/product

Returns all active products. Supports category, subcategory, page, limit query params.

GET /api/category

Returns all active product categories.

Other Public Endpoints

GET /api/banner

Returns all active banners ordered by position.

GET /api/testimonial

Returns all active testimonials ordered by position.

GET /api/outlet

Returns all active outlet locations.

POST /api/subscribe

Subscribe a user to the newsletter.

{ "email": "user@example.com", "name": "Jane Doe" }
GET /api/settings

Returns public site settings (company info, social links, business hours).

GET /api/home-section/[slug]

Returns a specific home page section by its slug identifier.

API Reference — Admin Authentication

All admin management endpoints require: Authorization: Bearer <token>
POST /api/admin/auth/login

Authenticates an admin user and returns a JWT token.

// Request
{ "email": "admin@example.com", "password": "your_password" }

// Response
{
  "success": true,
  "data": {
    "token": "eyJhbGci...",
    "user": { "name": "Admin", "email": "admin@example.com", "role": "admin" }
  }
}
GET /api/admin/auth/me

Returns the currently authenticated admin's profile. Requires Bearer token.

POST /api/admin/auth/password

Changes the admin password. Requires Bearer token.

{ "currentPassword": "old_password", "newPassword": "new_password" }

API Reference — Banners

MethodEndpointDescription
POST/api/admin/bannerCreate a new banner
GET/api/admin/bannerList all banners
PUT/api/admin/banner/[id]Update banner by ID
DELETE/api/admin/banner/[id]Delete banner by ID
PATCH/api/admin/banner/status/[id]Toggle active status
POST/api/admin/banner/sortReorder banners
// Create Banner — Request Body
{
  "title": "Welcome to Our Bakery",
  "description": "Fresh baked goods daily",
  "image": "https://res.cloudinary.com/...",
  "linkUrl": "/menu",
  "isActive": true
}

// Sort Banners — Request Body
{ "items": [{ "id": "banner_id", "position": 1 }, ...] }

API Reference — Blogs

MethodEndpointDescription
POST/api/admin/blogCreate a blog post
GET/api/admin/blogList all blogs (paginated)
PUT/api/admin/blog/[id]Update blog post
PATCH/api/admin/blog/status/[id]Toggle blog status
// Create Blog — Request Body
{
  "title": "Our New Summer Menu",
  "subtitle": "Refreshing treats for the season",
  "content": "<p>Rich HTML content from SunEditor...</p>",
  "image": "https://res.cloudinary.com/...",
  "category": "category_object_id",
  "tags": ["summer", "seasonal"],
  "isFeatured": false,
  "isShowHomepage": true,
  "status": "published"
}

Status values: pending | draft | published | archived

Blog category endpoints follow the same pattern under /api/admin/blog-category.

API Reference — Product Categories

MethodEndpointDescription
POST/api/admin/categoryCreate category
GET/api/admin/categoryList all categories
PUT/api/admin/category/[slug]Update category
PATCH/api/admin/category/status/[id]Toggle status
POST/api/admin/category/sortReorder categories
// Create Category — Request Body
{
  "name": "Cakes",
  "description": "All types of cakes",
  "image": "https://res.cloudinary.com/...",
  "parent": null  // ObjectId for sub-category, null for root
}

API Reference — Products

MethodEndpointDescription
POST/api/admin/productCreate a product
GET/api/admin/productList all products
PUT/api/admin/product/[id]Update product
// Create Product — Request Body
{
  "name": "Chocolate Fudge Cake",
  "description": "Rich chocolate cake with fudge frosting",
  "image": "https://res.cloudinary.com/...",
  "category": "category_object_id",
  "subcategory": "subcategory_object_id",
  "price": 29.99,
  "isPopular": true,
  "isFeatured": false,
  "status": "active"
}

API Reference — Outlets

MethodEndpointDescription
POST/api/admin/outletCreate outlet
GET/api/admin/outletList all outlets
PUT/api/admin/outlet/[id]Update outlet
PATCH/api/admin/outlet/status/[id]Toggle status
POST/api/admin/outlet/sortReorder outlets
// Create Outlet — Request Body
{
  "name": "Main Street Bakery",
  "address": "123 Main Street, Springfield",
  "phone": "1234567890",
  "dialCode": "+1",
  "image": "https://res.cloudinary.com/...",
  "mapLink": "https://maps.google.com/?q=...",
  "isShowHomepage": true,
  "businessHours": [
    { "day": "Monday", "open": "08:00", "close": "20:00", "isClosed": false },
    { "day": "Sunday", "open": "", "close": "", "isClosed": true }
  ]
}

API Reference — Testimonials

MethodEndpointDescription
POST/api/admin/testimonialCreate testimonial
GET/api/admin/testimonialList all testimonials
PUT/api/admin/testimonial/[id]Update testimonial
PATCH/api/admin/testimonial/status/[id]Toggle status
POST/api/admin/testimonial/sortReorder testimonials
// Create Testimonial — Request Body
{
  "name": "Sarah Johnson",
  "review": "Absolutely the best bakery in town!",
  "rating": 5,
  "image": "https://res.cloudinary.com/...",
  "authorRole": "Regular Customer",
  "companyName": ""
}

API Reference — File Upload

POST /api/admin/file

Uploads a file to Cloudinary. Requires Bearer token. Content-Type: multipart/form-data

FieldTypeDescription
fileFileThe file to upload (required)
folderstringOptional subfolder within Cloudinary folder
typestringimage | video | raw
// Response
{
  "success": true,
  "data": {
    "url": "https://res.cloudinary.com/your-cloud/image/upload/...",
    "publicId": "uploads/filename",
    "format": "jpg",
    "width": 1200,
    "height": 800
  }
}

API Reference — Settings

MethodEndpointDescription
GET/api/admin/settingsGet all settings
POST/api/admin/settings/generalUpdate general info
POST/api/admin/settings/cloudinaryUpdate Cloudinary credentials
POST/api/admin/settings/metadataUpdate SEO metadata
POST/api/admin/settings/page-bannerUpdate page banner images
POST/api/admin/settings/termsUpdate terms & privacy policy

API Reference — Misc

GET /api/admin/stats

Returns dashboard overview statistics.

{ "totalProducts": 48, "totalBlogs": 12, "totalOutlets": 3, "totalSubscribers": 210 }
GET /api/admin/subscribe

Returns all newsletter subscribers.

Database Models

User

{
  name:     String,             // Admin display name
  email:    String (unique),    // Login credential
  password: String,             // Bcrypt hash
  role:     "admin" | "user"
}

Blog

{
  title:         String,
  slug:          String (unique),
  subtitle:      String,
  content:       String,          // Rich HTML
  image:         String,          // Cloudinary URL
  status:        "pending" | "draft" | "published" | "archived",
  isFeatured:    Boolean,
  isShowHomepage: Boolean,
  publishedAt:   Date,
  tags:          [String],
  category:      ObjectId -> BlogCategory
}

BlogCategory

{
  name:        String,
  slug:        String (unique),
  description: String,
  status:      Boolean,
  position:    Number
}

Category (Product Category)

{
  name:        String,
  slug:        String (unique),
  description: String,
  image:       String,
  parent:      ObjectId | null,   // Self-reference for sub-categories
  status:      Boolean,
  position:    Number
}

Product

{
  name:        String,
  slug:        String (unique),
  description: String,
  image:       String,
  category:    ObjectId -> Category,
  subcategory: ObjectId -> Category,
  price:       Number,
  isPopular:   Boolean,
  isFeatured:  Boolean,
  status:      "active" | "inactive"
}

Outlet

{
  name:          String,
  slug:          String (unique),
  address:       String,
  phone:         String,
  dialCode:      String,
  image:         String,
  mapLink:       String,
  isShowHomepage: Boolean,
  status:        Boolean,
  position:      Number,
  businessHours: [{ day, open, close, isClosed }]
}

Banner

{
  title:       String,
  description: String,
  image:       String,
  linkUrl:     String,
  isActive:    Boolean,
  status:      Boolean,
  position:    Number,
  type:        String
}

Testimonial

{
  name:        String,
  review:      String,
  rating:      Number (1-5),
  image:       String,
  authorRole:  String,
  companyName: String,
  status:      Boolean,
  position:    Number
}

Settings (Single Document)

{
  general: {
    companyName, phone, email, address, logo, favicon,
    socialLinks: { facebook, instagram, twitter, youtube, linkedin }
  },
  pageBanner:  { menu, location, gallery, reserveTable },
  cloudinary:  { cloud_name, api_key, api_secret, folderName, secureUrlBase },
  metadata:    { title, applicationName, description, keywords[], openGraphImage },
  termsPolicy: { terms, policy },
  businessHours: [{ day, open, close, isClosed }]
}

Authentication

The template uses NextAuth v5 with a Credentials provider for page-level session management, alongside a custom JWT implementation for API route protection.

Login Flow

1. POST /api/admin/auth/login  →  { email, password }
2. Server verifies password with bcrypt
3. Generates JWT token (30-day expiry) via jsonwebtoken
4. Client stores token in NextAuth session
5. Subsequent requests include:  Authorization: Bearer <token>
6. Protected API routes verify the token via lib/authenticate.ts

Protecting Custom API Routes

import { asyncHandler } from "@/lib/async-handler";

// This route is protected — unauthenticated requests get 401
export const GET = asyncHandler(
  async (req) => {
    return Response.json({ success: true, data: "protected data" });
  },
  { checkAuth: true }
);

Cloudinary Setup

  1. Create a free account at cloudinary.com
  2. Copy your Cloud Name, API Key, and API Secret from the Cloudinary dashboard
  3. Log in to your admin dashboard and go to Settings → Cloudinary
  4. Enter your credentials and click Save
ℹ️ Note All Cloudinary credentials are stored securely in the MongoDB Settings document — not in environment variables — so you can update them any time from the admin panel without redeploying.

Supported Upload Types

TypeSupported Formats
ImagesJPG, PNG, GIF, WebP, SVG
VideosMP4, MOV, AVI
RawPDF, DOC, etc.

Admin Dashboard

Access the admin dashboard at /admin/login using your admin credentials.

SectionWhat You Can Do
DashboardView overview stats: products, blogs, subscribers, outlets
BannersAdd, edit, delete, activate/deactivate, drag-to-reorder hero banners
ProductsAdd menu items with images, pricing, category assignment
CategoriesManage product categories and sub-categories with ordering
BlogsFull blog management with SunEditor rich text, tags, featured flags
Blog CategoriesOrganize blogs into categories with ordering
OutletsManage branch locations with business hours, phone, map links
TestimonialsCustomer reviews with rating, image, drag-to-reorder
SubscribersView newsletter subscriber list
SettingsGeneral info, Cloudinary, SEO metadata, page banners, T&C

State Management

The application uses Zustand for lightweight client-side state. Four stores are provided:

StoreFilePurpose
useBlogCategoryStorestore/useBlogCategoryStore.tsActive blog category filter on the public blog page
useCategoryStorestore/useCategoryStore.tsActive product category filter on the menu page
useTableStorestore/useTableStore.tsAdmin data table sorting, pagination, and filter state
useUserProfilestore/useUserProfile.tsAuthenticated admin profile data
import { useBlogCategoryStore } from "@/store/useBlogCategoryStore";

const { activeCategory, setActiveCategory } = useBlogCategoryStore();

Customization Guide

Changing Brand Colors

/* In app/globals.css */
:root {
  --primary: 25 75% 40%;  /* HSL value */
}

Changing Fonts

Fonts are loaded in app/layout.tsx via next/font/google. Replace the current fonts with any Google Font:

import { Montserrat } from "next/font/google";
const font = Montserrat({ subsets: ["latin"], weight: ["400", "700"] });

Adding a New Public Page

  1. Create a folder: app/(public)/your-page/
  2. Add page.tsx inside it
  3. Build your page using existing components from components/features/ and components/ui/

Adding a New Admin Section

  1. Create page: app/(private)/admin/dashboard/your-section/page.tsx
  2. Add API route: app/api/admin/your-section/route.ts
  3. Add Mongoose model: model/YourModel.ts
  4. Add validation: update lib/validation-schema.ts
  5. Add server actions: actions/your-section/

Modifying Validation Rules

All Zod schemas are centralized in lib/validation-schema.ts:

export const createProductSchema = z.object({
  name:  z.string().min(2).max(200),
  price: z.number().positive(),
  // add your custom fields here
});

Deployment

Deploy on Vercel (Recommended)

  1. Push your project to a GitHub repository
  2. Import the repository on vercel.com
  3. Add all environment variables under Project Settings → Environment Variables
  4. Click Deploy — Vercel handles the rest automatically

Deploy on a VPS / Dedicated Server

# 1. Build the application
pnpm build

# 2. Start the production server
pnpm start
# Runs on port 3000 by default

Use Nginx or Caddy as a reverse proxy to expose the app on port 80/443:

# Sample Nginx configuration
server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Process Manager (PM2)

npm install -g pm2
pm2 start "pnpm start" --name template-bakery
pm2 save
pm2 startup

Changelog

v1.0.0 — Initial Release

Support

For support, questions, and bug reports, please use the Comments section on the item's CodeCanyon / ThemeForest page.

When reporting an issue, please include:

Response Time We typically respond to support questions within 24–48 business hours.