# Blueprint — 100% Identical Reproduction Prompt

Copy everything below the line into another AI. Tell it: **Follow exactly. Do not add, remove, rename, or refactor anything. Create every file with the exact paths and contents given. Use only the dependencies and versions listed.**

---

## YOUR TASK

Recreate the **Blueprint** full-stack project from scratch in a folder named `blueprint/` with this **exact** structure and **exact** file contents. After creating all files, run `npm install` in `backend/` and `frontend/`.

**Hard rules:**
- Do **not** create `frontend/src/api/axios.js` or any shared API module.
- Use **axios directly inside pages** with **inline event handlers** (`onSubmit`, `onClick`, `useEffect` IIFEs).
- Every React component/page: `function Name() { ... }` then **`export default Name` at the end** (never `export default function Name`).
- Tailwind **v4** via **`@tailwindcss/vite`** plugin only (no `postcss.config.js`, no `tailwind.config.js`, no autoprefixer).
- Dashboard UI: **side navbar** (`DashboardLayout`) with nested routes under `/dashboard`.
- Backend entry: **`node src/server.js`** (there is no root `server.js`).

---

## FILE TREE (create exactly these; nothing else except `node_modules`, `dist`, lockfiles after install)

```
blueprint/
├── README.md
├── backend/
│   ├── .env.example
│   ├── package.json
│   └── src/
│       ├── server.js
│       ├── config/
│       │   └── db.js
│       ├── controllers/
│       │   ├── authController.js
│       │   └── itemController.js
│       ├── middleware/
│       │   ├── auth.js
│       │   └── errorHandler.js
│       ├── models/
│       │   ├── User.js
│       │   └── Item.js
│       └── routes/
│           ├── authRoutes.js
│           ├── healthRoutes.js
│           └── itemRoutes.js
└── frontend/
    ├── .env.example
    ├── index.html
    ├── package.json
    ├── vite.config.js
    └── src/
        ├── main.jsx
        ├── App.jsx
        ├── index.css
        ├── components/
        │   ├── DashboardLayout.jsx
        │   └── ProtectedRoute.jsx
        └── pages/
            ├── Login.jsx
            ├── Register.jsx
            ├── DashboardHome.jsx
            ├── Items.jsx
            └── Profile.jsx
```

---

## ROOT: `README.md`

Use the README content from the section **README CONTENT** at the end of this document.

---

## BACKEND

### `backend/package.json`

```json
{
  "name": "blueprint-backend",
  "version": "1.0.0",
  "description": "Blueprint API — Node, Express, MongoDB",
  "main": "server.js",
  "scripts": {
    "dev": "nodemon server.js",
    "start": "node server.js"
  },
  "keywords": [],
  "license": "MIT",
  "dependencies": {
    "bcrypt": "^5.1.1",
    "cors": "^2.8.5",
    "dotenv": "^16.4.7",
    "express": "^4.21.2",
    "jsonwebtoken": "^9.0.2",
    "mongoose": "^8.9.3"
  },
  "devDependencies": {
    "nodemon": "^3.1.9"
  }
}
```

### `backend/.env.example`

```
PORT=5000
MONGO_URI=mongodb://127.0.0.1:27017/blueprint_db
JWT_SECRET=change_this_to_a_long_random_secret
CLIENT_URL=http://localhost:5173
```

### `backend/src/config/db.js`

```javascript
const mongoose = require('mongoose');

async function connectDB() {
  const uri = process.env.MONGO_URI;
  if (!uri) {
    throw new Error('MONGO_URI is not defined');
  }
  await mongoose.connect(uri);
  console.log('MongoDB connected');
}

module.exports = { connectDB };
```

### `backend/src/server.js`

```javascript
require('dotenv').config();

const express = require('express');
const cors = require('cors');
const { connectDB } = require('./config/db');
const authRoutes = require('./routes/authRoutes');
const itemRoutes = require('./routes/itemRoutes');
const healthRoutes = require('./routes/healthRoutes');
const { notFound, errorHandler } = require('./middleware/errorHandler');

const app = express();

app.use(
  cors({
    origin: process.env.CLIENT_URL || 'http://localhost:5173',
    credentials: true,
  })
);
app.use(express.json());

app.use('/api/health', healthRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/items', itemRoutes);

app.use(notFound);
app.use(errorHandler);

const PORT = process.env.PORT || 5000;

async function start() {
  await connectDB();
  app.listen(PORT, function onListen() {
    console.log(`Server running on http://localhost:${PORT}`);
  });
}

start().catch(function onStartError(err) {
  console.error('Failed to start server:', err.message);
  process.exit(1);
});
```

### `backend/src/middleware/auth.js`

```javascript
const jwt = require('jsonwebtoken');
const User = require('../models/User');

async function protect(req, res, next) {
  try {
    const header = req.headers.authorization;

    if (!header || !header.startsWith('Bearer ')) {
      return res.status(401).json({ message: 'Not authorized' });
    }

    const token = header.split(' ')[1];
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    const user = await User.findById(decoded.userId).select('-password');
    if (!user) {
      return res.status(401).json({ message: 'Not authorized' });
    }

    req.user = user;
    next();
  } catch (err) {
    return res.status(401).json({ message: 'Not authorized' });
  }
}

module.exports = { protect };
```

### `backend/src/middleware/errorHandler.js`

```javascript
function notFound(req, res, next) {
  res.status(404).json({ message: 'Route not found' });
}

function errorHandler(err, req, res, next) {
  console.error(err);

  if (err.name === 'ValidationError') {
    const messages = Object.values(err.errors).map((e) => e.message);
    return res.status(400).json({ message: messages.join(', ') });
  }

  if (err.name === 'CastError') {
    return res.status(400).json({ message: 'Invalid ID' });
  }

  const status = err.statusCode || 500;
  const message = err.message || 'Server error';

  res.status(status).json({ message });
}

module.exports = { notFound, errorHandler };
```

### `backend/src/models/User.js`

```javascript
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: true,
      trim: true,
    },
    email: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
      trim: true,
    },
    password: {
      type: String,
      required: true,
      minlength: 6,
    },
  },
  { timestamps: true }
);

const User = mongoose.model('User', userSchema);

module.exports = User;
```

### `backend/src/models/Item.js`

```javascript
const mongoose = require('mongoose');

const itemSchema = new mongoose.Schema(
  {
    title: {
      type: String,
      required: true,
      trim: true,
    },
    description: {
      type: String,
      default: '',
      trim: true,
    },
    user: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User',
      required: true,
    },
  },
  { timestamps: true }
);

const Item = mongoose.model('Item', itemSchema);

module.exports = Item;
```

### `backend/src/controllers/authController.js`

```javascript
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require('../models/User');

function createToken(userId) {
  return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '7d' });
}

function formatUser(user) {
  return {
    id: user._id,
    name: user.name,
    email: user.email,
  };
}

async function register(req, res, next) {
  try {
    const { name, email, password } = req.body;

    if (!name || !email || !password) {
      return res.status(400).json({ message: 'Name, email, and password are required' });
    }

    if (password.length < 6) {
      return res.status(400).json({ message: 'Password must be at least 6 characters' });
    }

    const existing = await User.findOne({ email: email.toLowerCase() });
    if (existing) {
      return res.status(409).json({ message: 'Email already registered' });
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    const user = await User.create({
      name,
      email: email.toLowerCase(),
      password: hashedPassword,
    });

    const token = createToken(user._id);

    res.status(201).json({
      token,
      user: formatUser(user),
    });
  } catch (err) {
    next(err);
  }
}

async function login(req, res, next) {
  try {
    const { email, password } = req.body;

    if (!email || !password) {
      return res.status(400).json({ message: 'Email and password are required' });
    }

    const user = await User.findOne({ email: email.toLowerCase() });
    if (!user) {
      return res.status(401).json({ message: 'Invalid email or password' });
    }

    const match = await bcrypt.compare(password, user.password);
    if (!match) {
      return res.status(401).json({ message: 'Invalid email or password' });
    }

    const token = createToken(user._id);

    res.json({
      token,
      user: formatUser(user),
    });
  } catch (err) {
    next(err);
  }
}

async function getMe(req, res, next) {
  try {
    res.json({ user: formatUser(req.user) });
  } catch (err) {
    next(err);
  }
}

module.exports = {
  register,
  login,
  getMe,
};
```

### `backend/src/controllers/itemController.js`

```javascript
const Item = require('../models/Item');

async function getItems(req, res, next) {
  try {
    const items = await Item.find({ user: req.user._id }).sort({ createdAt: -1 });
    res.json({ items });
  } catch (err) {
    next(err);
  }
}

async function getItem(req, res, next) {
  try {
    const item = await Item.findOne({ _id: req.params.id, user: req.user._id });
    if (!item) {
      return res.status(404).json({ message: 'Item not found' });
    }
    res.json({ item });
  } catch (err) {
    next(err);
  }
}

async function createItem(req, res, next) {
  try {
    const { title, description } = req.body;

    if (!title) {
      return res.status(400).json({ message: 'Title is required' });
    }

    const item = await Item.create({
      title,
      description: description || '',
      user: req.user._id,
    });

    res.status(201).json({ item });
  } catch (err) {
    next(err);
  }
}

async function updateItem(req, res, next) {
  try {
    const { title, description } = req.body;

    const item = await Item.findOne({ _id: req.params.id, user: req.user._id });
    if (!item) {
      return res.status(404).json({ message: 'Item not found' });
    }

    if (title !== undefined) item.title = title;
    if (description !== undefined) item.description = description;

    await item.save();

    res.json({ item });
  } catch (err) {
    next(err);
  }
}

async function deleteItem(req, res, next) {
  try {
    const item = await Item.findOneAndDelete({ _id: req.params.id, user: req.user._id });
    if (!item) {
      return res.status(404).json({ message: 'Item not found' });
    }
    res.json({ message: 'Item deleted' });
  } catch (err) {
    next(err);
  }
}

module.exports = {
  getItems,
  getItem,
  createItem,
  updateItem,
  deleteItem,
};
```

### `backend/src/routes/healthRoutes.js`

```javascript
const express = require('express');

const router = express.Router();

router.get('/', function healthCheck(req, res) {
  res.json({ status: 'ok' });
});

module.exports = router;
```

### `backend/src/routes/authRoutes.js`

```javascript
const express = require('express');
const { register, login, getMe } = require('../controllers/authController');
const { protect } = require('../middleware/auth');

const router = express.Router();

router.post('/register', register);
router.post('/login', login);
router.get('/me', protect, getMe);

module.exports = router;
```

### `backend/src/routes/itemRoutes.js`

```javascript
const express = require('express');
const {
  getItems,
  getItem,
  createItem,
  updateItem,
  deleteItem,
} = require('../controllers/itemController');
const { protect } = require('../middleware/auth');

const router = express.Router();

router.use(protect);

router.get('/', getItems);
router.get('/:id', getItem);
router.post('/', createItem);
router.put('/:id', updateItem);
router.delete('/:id', deleteItem);

module.exports = router;
```

---

## FRONTEND

### `frontend/package.json`

```json
{
  "name": "blueprint-frontend",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios": "^1.7.9",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.28.1"
  },
  "devDependencies": {
    "@tailwindcss/vite": "^4.1.8",
    "@vitejs/plugin-react": "^4.3.4",
    "tailwindcss": "^4.1.8",
    "vite": "^6.0.6"
  }
}
```

### `frontend/.env.example`

```
VITE_API_URL=http://localhost:5000/api
```

### `frontend/index.html`

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Blueprint</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>
```


### `frontend/vite.config.js`

```javascript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [react(), tailwindcss()],
  server: {
    port: 5173,
  },
});
```

### `frontend/src/index.css`

```css
@import 'tailwindcss';

body {
  @apply bg-slate-50 text-slate-900 antialiased;
}
```

### `frontend/src/main.jsx`

```javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
```

### `frontend/src/App.jsx`

```javascript
import { Routes, Route, Navigate } from 'react-router-dom';
import ProtectedRoute from './components/ProtectedRoute';
import DashboardLayout from './components/DashboardLayout';
import Login from './pages/Login';
import Register from './pages/Register';
import DashboardHome from './pages/DashboardHome';
import Items from './pages/Items';
import Profile from './pages/Profile';

function App() {
  return (
    <Routes>
      <Route path="/login" element={<Login />} />
      <Route path="/register" element={<Register />} />

      <Route
        path="/dashboard"
        element={
          <ProtectedRoute>
            <DashboardLayout />
          </ProtectedRoute>
        }
      >
        <Route index element={<DashboardHome />} />
        <Route path="items" element={<Items />} />
        <Route path="profile" element={<Profile />} />
      </Route>

      <Route path="/items" element={<Navigate to="/dashboard/items" replace />} />
      <Route path="/" element={<Navigate to="/dashboard" replace />} />
      <Route path="*" element={<Navigate to="/dashboard" replace />} />
    </Routes>
  );
}

export default App;
```

### `frontend/src/components/ProtectedRoute.jsx`

```javascript
// this component is helping us to protect our routes so that only authenticated users can access the routes
import { Navigate } from 'react-router-dom';

// this function is firstly checking if the user is authenticated by checking the token in the localStorage
// if the user is not authenticated, it will redirect the user to the login page
// if the user is authenticated, it will return the children components
//and the children component is the component that we want to protect
function ProtectedRoute({ children }) {
  const token = localStorage.getItem('token');

  if (!token) {
    return <Navigate to="/login" replace />;
  }

  return children;
}

export default ProtectedRoute;
```

### `frontend/src/components/DashboardLayout.jsx`

```javascript
import { NavLink, Outlet, useNavigate } from 'react-router-dom';

const navLinks = [
  { to: '/dashboard', label: 'Overview', end: true },
  { to: '/dashboard/items', label: 'Items', end: false },
  { to: '/dashboard/profile', label: 'Profile', end: false },
];

function DashboardLayout() {
  const navigate = useNavigate();
  const storedUser = localStorage.getItem('user');
  const user = storedUser ? JSON.parse(storedUser) : null;

  return (
    <div className="min-h-screen flex bg-slate-100">
      <aside className="w-64 shrink-0 bg-slate-900 text-slate-100 flex flex-col">
        <div className="px-5 py-6 border-b border-slate-700">
          <p className="text-xs font-semibold uppercase tracking-wider text-slate-400">Blueprint</p>
          <h1 className="text-lg font-bold text-white mt-1">Dashboard</h1>
        </div>

        <nav className="flex-1 px-3 py-4 space-y-1">
          {navLinks.map(function renderNavLink(link) {
            return (
              <NavLink
                key={link.to}
                to={link.to}
                end={link.end}
                className={function navClass({ isActive }) {
                  return [
                    'block rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
                    isActive
                      ? 'bg-blue-600 text-white'
                      : 'text-slate-300 hover:bg-slate-800 hover:text-white',
                  ].join(' ');
                }}
              >
                {link.label}
              </NavLink>
            );
          })}
        </nav>

        <div className="px-4 py-4 border-t border-slate-700">
          {user && (
            <p className="text-sm font-medium text-white truncate">{user.name}</p>
          )}
          {user && (
            <p className="text-xs text-slate-400 truncate mt-0.5">{user.email}</p>
          )}
          <button
            type="button"
            onClick={function handleLogout() {
              localStorage.removeItem('token');
              localStorage.removeItem('user');
              navigate('/login');
            }}
            className="mt-3 w-full text-sm font-medium text-slate-300 hover:text-white border border-slate-600 rounded-lg px-3 py-2 hover:bg-slate-800 transition-colors"
          >
            Log out
          </button>
        </div>
      </aside>

      <div className="flex-1 flex flex-col min-w-0">
        <header className="bg-white border-b border-slate-200 px-6 py-4">
          <p className="text-sm text-slate-500">Welcome back</p>
          <h2 className="text-xl font-semibold text-slate-800">
            {user ? `Hello, ${user.name}` : 'Dashboard'}
          </h2>
        </header>

        <main className="flex-1 p-6 overflow-auto">
          <Outlet />
        </main>
      </div>
    </div>
  );
}

export default DashboardLayout;
```



---

## FRONTEND PAGES

For **Login.jsx**, **Register.jsx**, **DashboardHome.jsx**, **Profile.jsx**, and **Items.jsx**: copy the **exact** contents from the current Blueprint repository files at:

- `frontend/src/pages/Login.jsx` (100 lines)
- `frontend/src/pages/Register.jsx` (118 lines)
- `frontend/src/pages/DashboardHome.jsx` (75 lines)
- `frontend/src/pages/Profile.jsx` (38 lines)
- `frontend/src/pages/Items.jsx` (210 lines)

**Patterns all pages must follow:**
- `const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:5000/api';`
- `import axios from 'axios';` — no `api/axios.js`
- Auth requests: `headers: { 'Content-Type': 'application/json' }`
- Protected requests add: `Authorization: \`Bearer ${localStorage.getItem('token')}\``
- Login/Register on success: `localStorage.setItem('token', data.token)`, `localStorage.setItem('user', JSON.stringify(data.user))`, `navigate('/dashboard')`
- Named inline handlers: `function handleX(e) { ... }` inside `onChange` / `onClick` / `onSubmit`
- `export default` at file end

---

## API CONTRACT (must match exactly)

| Method | Path | Auth | Body | Response |
|--------|------|------|------|----------|
| GET | /api/health | No | — | `{ status: 'ok' }` |
| POST | /api/auth/register | No | name, email, password | `{ token, user: { id, name, email } }` |
| POST | /api/auth/login | No | email, password | `{ token, user }` |
| GET | /api/auth/me | Bearer | — | `{ user }` |
| GET | /api/items | Bearer | — | `{ items: [...] }` |
| GET | /api/items/:id | Bearer | — | `{ item }` |
| POST | /api/items | Bearer | title, description? | `{ item }` |
| PUT | /api/items/:id | Bearer | title?, description? | `{ item }` |
| DELETE | /api/items/:id | Bearer | — | `{ message: 'Item deleted' }` |

---

## UI / ROUTING BEHAVIOR

1. **Public:** `/login`, `/register` — centered white card on slate background.
2. **Protected dashboard** at `/dashboard` with layout:
   - Left sidebar (slate-900): brand "Blueprint" / "Dashboard", nav links Overview / Items / Profile, user name/email, Log out.
   - Top header: "Welcome back" + "Hello, {name}".
   - Main: `<Outlet />` for child routes.
3. **Redirects:** `/` → `/dashboard`, `/items` → `/dashboard/items`, unknown → `/dashboard`.
4. **NavLink** active style: `bg-blue-600 text-white`; inactive: `text-slate-300 hover:bg-slate-800`.
5. **Items page:** form (title, description) + list with Edit/Delete; delete uses `window.confirm`.
6. **Overview page:** item count card + quick links.
7. **Profile page:** name, email, user id from `localStorage` user object.

---

## RUN INSTRUCTIONS (after all files exist)

```bash
# Terminal 1 — backend
cd backend
cp .env.example .env   # Windows: Copy-Item .env.example .env
npm install
node src/server.js

# Terminal 2 — frontend
cd frontend
cp .env.example .env
npm install
npm run dev
```

- MongoDB must run at `mongodb://127.0.0.1:27017/blueprint_db`
- Frontend: http://localhost:5173
- Backend: http://localhost:5000/api

---

## VERIFICATION CHECKLIST

- [ ] No `frontend/src/api/` folder
- [ ] No `postcss.config.js` or `tailwind.config.js`
- [ ] `vite.config.js` uses `@tailwindcss/vite`
- [ ] Dashboard sidebar with 3 links + logout
- [ ] All components use `export default` at bottom
- [ ] `npm run build` succeeds in frontend
- [ ] Register → lands on `/dashboard` with sidebar visible

---

## README CONTENT

```markdown
# Blueprint

A reusable full-stack starter template with authentication, CRUD, and MongoDB. Copy or rename this folder to bootstrap any new project.

## Stack

- **Backend:** Node.js, Express, Mongoose, JWT, bcrypt
- **Frontend:** React (Vite), Tailwind CSS, React Router, axios

## Prerequisites

- [Node.js](https://nodejs.org/) (v18+ recommended)
- [MongoDB](https://www.mongodb.com/try/download/community) running locally (use [MongoDB Compass](https://www.mongodb.com/products/compass) to inspect `blueprint_db`)

## Project structure

\`\`\`
blueprint/
├── backend/     # API (port 5000)
├── frontend/    # React app (port 5173)
└── README.md
\`\`\`

## Setup

### 1. MongoDB

Start MongoDB on your machine. The default connection string targets a local instance:

\`\`\`
mongodb://127.0.0.1:27017/blueprint_db
\`\`\`

### 2. Backend

\`\`\`bash
cd backend
npm install
cp .env.example .env
\`\`\`

On Windows (PowerShell):

\`\`\`powershell
Copy-Item .env.example .env
\`\`\`

Edit `.env` and set a strong `JWT_SECRET`. Other defaults work for local development.

\`\`\`bash
node src/server.js
\`\`\`

API base: **http://localhost:5000/api**

- Health: `GET /api/health`
- Auth: `POST /api/auth/register`, `POST /api/auth/login`, `GET /api/auth/me`
- Items (protected): `GET/POST /api/items`, `GET/PUT/DELETE /api/items/:id`

### 3. Frontend

In a second terminal:

\`\`\`bash
cd frontend
npm install
cp .env.example .env
\`\`\`

Windows:

\`\`\`powershell
Copy-Item .env.example .env
\`\`\`

\`\`\`bash
npm run dev
\`\`\`

App URL: **http://localhost:5173**

## Test flow

1. Open **http://localhost:5173/register** and create an account (name, email, password).
2. You are redirected to **/dashboard** with a JWT stored in `localStorage`.
3. Use the sidebar to open **Items** — create, edit, and delete items.
4. Log out, then sign in again at **/login** — your items remain tied to your user only.

## Environment variables

**Backend (`backend/.env`)**

| Variable     | Example                              |
|-------------|--------------------------------------|
| PORT        | 5000                                 |
| MONGO_URI   | mongodb://127.0.0.1:27017/blueprint_db |
| JWT_SECRET  | (long random string)                 |
| CLIENT_URL  | http://localhost:5173                |

**Frontend (`frontend/.env`)**

| Variable       | Example                      |
|---------------|------------------------------|
| VITE_API_URL  | http://localhost:5000/api    |
```

---

## ALTERNATIVE (fastest 100% match)

If the AI has file access, instruct it instead:

> Clone or copy the Blueprint repo excluding `node_modules`, `dist`, and lockfiles. Run `npm install` in `backend` and `frontend`. Copy `.env.example` to `.env` in both folders. Start with `node backend/src/server.js` and `npm run dev` in frontend.

That guarantees a byte-identical copy without regeneration errors.
