A production-ready, pedagogical starting point for teaching modern full-stack web development to L3 students.
This is a minimal but complete full-stack web application demonstrating:
- ✅ RESTful API with Node.js + Express
- ✅ Modern React with Hooks and React Router
- ✅ MySQL database with proper schema design
- ✅ Input validation and error handling
- ✅ Docker Compose orchestration
- ✅ Production-ready patterns (health checks, logging, PropTypes)
Perfect for: Teaching L3 (3rd-year undergraduate) students full-stack development with industry best practices.
Frontend (React + Vite + Tailwind)
↓ HTTP/JSON
Backend (Node.js + Express)
↓ SQL
Database (MySQL 8.0)
Key Features:
- 📦 Product catalog with categories
- 🔍 Pagination and filtering
- ✏️ Full CRUD operations
- 🔐 SQL injection protection
- ✅ Input validation
- 🏥 Health monitoring
- 🐳 Docker-first development
See LEARNING_NOTES.md for detailed learning objectives.
-
Recommended for all students for easiest setup.
- Install git (http://git-scm.com) with default options
- Install Docker Desktop (https://docker.com) Remove existing MySQL installations to avoid port conflicts.
- Install VS Code (https://code.visualstudio.com)
-
Alternatively, if you cannot use docker it will need more manual setup:
- install Node.js 22+ (https://nodejs.org)
- MySQL 8.0+ locally
- Create a MySQL database and user as per
db/init.sql(in code see below).
Get the source code locally:
- Clone the Git repository using the VS Code UI (Recommended):
- View > Command Palette… → type and select “Git: Clone” (or on the Welcome page click “Clone Git Repository…”)
- Paste:
https://github.com/ebpro/fullstack-minimal-app.git - Choose a destination folder, then click “Open” when prompted
- If docker is installed, VS Code will prompt to reopen in container - accept this
- Or download ZIP and extract:
- https://github.com/ebpro/fullstack-minimal-app/archive/refs/heads/develop.zip
cd fullstack-minimal-app-develop/
Run the following command in the application directory to start the app:
docker compose up --buildFirst Time Setup:
- Open project in VS Code
- File > Open Folder... → select minimal-app directory
- Reopen in container
- Click "Reopen in Container" notification
- OR: Cmd/Ctrl+Shift+P → "Dev Containers: Reopen in Container"
- Wait for build (first time: 2-5 minutes)
- Open terminal in VS Code (Ctrl+`) and run:
# From project root (inside container)
npm install # Install dependencies (if not auto-installed)
npm run dev # Start both frontend and backend- Open browser (if not auto-opened) Frontend: http://localhost:5173 Backend API: http://localhost:4000
Daily Workflow:
# Open VS Code → it reopens in container automatically
# Terminal already connected to container
npm run dev # Start development servers
# Code, save, see changes instantly (hot reload)WARNING: This is not the recommended way to run the app. Use Docker and Dev Containers if possible. If you must run locally, ensure you have preceeding prerequisites (node, mysql and database created) installed and configured.
# Install dependencies for both workspaces (backend + frontend)
npm install
# Start both dev servers at once (uses npm workspaces + concurrently)
npm run dev
# Backend health: http://localhost:4000/health
# Frontend (Vite): http://localhost:5173/
# Scraper service: http://localhost:5000/healthNote: copying .env.example into .env is optional — the compose file and app code include sensible defaults for typical local development.
fullstack-minimal-app/
├── backend/
│ ├── src/
│ │ ├── index.js # Main server (middleware, routes)
│ │ ├── db.js # MySQL connection pool
│ │ ├── middleware/
│ │ │ ├── errorHandler.js # Centralized error handling
│ │ │ ├── validators.js # Input validation rules
│ │ │ └── staticServing.js # Optional frontend serving
│ │ └── routes/
│ │ ├── products.js # Product CRUD endpoints
│ │ └── categories.js # Category endpoints
│ ├── test/
│ │ └── smoke.js # Smoke tests with assertions
│ ├── package.json
│ ├── .env.example
│ ├── eslint.config.js
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── App.jsx # Main component (routing, health check)
│ │ ├── main.jsx # React entry point
│ │ ├── styles.css # Tailwind imports
│ │ └── components/
│ │ ├── ProductList.jsx # Product listing with pagination
│ │ ├── ProductDetail.jsx # Single product view
│ │ └── ProductForm.jsx # Create/edit form
│ ├── index.html # Vite entry point
│ ├── package.json
│ ├── .env.example
│ ├── eslint.config.js
│ ├── postcss.config.cjs
│ ├── tailwind.config.cjs
│ ├── vite.config.js
│ └── Dockerfile
├── scraper-service/
│ ├── src/
│ │ ├── index.js # Mock review scraper service
│ │ └── mockReviews.js # Mock review data generator
│ ├── package.json
│ └── Dockerfile
├── db/
│ └── init.sql # Database schema + seed data
├── docker-compose.yml # Orchestration with health checks
├── package.json # Root workspace configuration
├── README.md # This file
└── LEARNING_NOTES.md # Student learning guideStructure Purpose:
frontend/src/- React application with components and stylingbackend/src/- Express REST API with routes, middleware, and database connectionscraper-service/src/- Mock microservice for external data demonstrationdb/- Database schema and initialization- Root
package.json- npm workspace configuration for managing all services
| Method | Endpoint | Description | Query Params |
|---|---|---|---|
| GET | /api/products |
List products (paginated) | page, per_page, category_id |
| GET | /api/products/:id |
Get single product | - |
| POST | /api/products |
Create product | - |
| PUT | /api/products/:id |
Update product | - |
| DELETE | /api/products/:id |
Delete product | - |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/categories |
List all categories |
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check (includes DB status) |
Example Request:
curl http://localhost:4000/api/products?page=1&per_page=10Example Response:
{
"data": [
{
"id": 1,
"name": "USB-C Charger",
"description": "Fast 30W USB-C charger",
"price": 19.99,
"image_url": "https://placehold.co/600x400?text=USB-C+Charger",
"category_id": 1,
"category_name": "Electronics",
"created_at": "2025-10-01T10:00:00.000Z"
}
],
"meta": {
"total": 20,
"page": 1,
"per_page": 10,
"total_pages": 2
}
}- To run commands, open a terminal in VS Code (Ctrl+`) inside the Dev Container.
npm run dev # Start with nodemon (auto-reload)
npm start # Start in production mode
npm test # Run smoke tests
npm run lint # Run ESLint-
Open the file
api-tests.httpat the repo root. -
Install the "REST Client" VS Code extension (if not already present).
-
Click "Send Request" above any request to exercise the Backend and Scraper endpoints.
-
Tip: Adjust
@backendUrland@scraperUrlvariables at the top if you changed ports. -
ADVANCED STUDENTS ONLY : To manage dependencies (run from project root):
npm install # Install dependencies
npm ci # Clean install (fresh node_modules)
npm outdated # Check for outdated packages
npm update # Update packages (use cautiously)Cause: When running Vite inside a container, it must bind to 0.0.0.0 (not localhost) to be reachable from the host.
Fixes:
- Check
frontend/.envhasVITE_HOST=0.0.0.0 - Start the frontend manually inside the container:
cd frontend
npm run dev -- --host 0.0.0.0If ports 5173, 4000, or 3306 are in use on your host, stop the conflicting services or change port mappings in docker-compose.yml.
Cause: Another app is using the same port.
Fixes:
- Stop the other app (e.g., Remove Docker containers from docker desktop UI, stop local MySQL server).
This project uses Docker for easy setup and consistent environments. The easiest way to manage docker is via the Docker Desktop application.
- Dev Containers: SQLTools and the MySQL driver are already installed and preconfigured. Open SQLTools in VS Code and connect using the provided MySQL profile. No manual setup needed.
- Local VS Code (outside the container): if you prefer a local connection, add a profile with these defaults and be sure containers are running:
- Host: 127.0.0.1, Port: 3306
- Database: minimal_app_db
- User/Password: appuser / apppassword
Note: These values come from docker-compose. If you changed MYSQL_DATABASE/USER/PASSWORD, use your custom values. Avoid exposing DB ports in production.
- Dev Containers docs: https://code.visualstudio.com/docs/devcontainers/containers
- Express best practices: https://expressjs.com/en/advanced/best-practice-performance.html
- React documentation: https://react.dev/
This is a teaching project. Contributions that improve pedagogy are welcome. Please keep changes small, comment "why" as well as "what", and update LEARNING_NOTES.md for new exercises.
| Layer | Technology | Version |
|---|---|---|
| Frontend | React | 19.1 |
| Frontend | Vite | 7.1 |
| Frontend | Tailwind CSS | 3.4 |
| Frontend | React Router | 7.9 |
| Backend | Node.js | 22 |
| Backend | Express | 4.18 |
| Database | MySQL | 8.0 |
MIT License - Free for educational use
For Students: Read LEARNING_NOTES.md first. Issues: Open an issue for bugs or pedagogical improvements.