React frontend + Node/Express API + PostgreSQL (or other DB)
burger-shop/ // root (monorepo / two folders) │ ├── server/ // Node/Express backend │ ├── src/ │ │ ├── models/ // Sequelize / Knex models │ │ ├── routes/ // API routes (burgers, cart) │ │ └── index.js // express app │ └── package.json │ ├── client/ // React app (create-react-app / Vite) │ ├── src/ │ │ ├── App.jsx │ │ ├── components/BurgerList.jsx │ │ └── context/ // CartContext │ └── package.json │ ├── migrations/ // DB migrations └── README.md
We keep server and client separated for clarity — same flow as other stacks: DB ↔ API ↔ Frontend.
Sequelize model for burgers (server/src/models/burger.js):
// server/src/models/burger.js module.exports = (sequelize, DataTypes) => { const Burger = sequelize.define('Burger', { name: { type: DataTypes.STRING, allowNull: false }, price: { type: DataTypes.DECIMAL(6,2), allowNull: false } }); return Burger; };
Use migrations to create the table and seed initial data.
Minimal Express routes (server/src/routes/burgers.js):
// server/src/routes/burgers.js const express = require('express'); const router = express.Router(); const { Burger } = require('../models'); /* GET /api/burgers */ router.get('/', async (req, res) => { const burgers = await Burger.findAll(); res.json(burgers); }); /* POST /api/cart */ router.post('/cart', async (req, res) => { // handle add to cart (session, DB cart table, or persisted cart) res.json({ success: true }); }); module.exports = router;
Server entry (server/src/index.js):
// server/src/index.js const express = require('express'); const burgersRoutes = require('./routes/burgers'); const app = express(); app.use(express.json()); app.use('/api/burgers', burgersRoutes); app.use('/api/cart', burgersRoutes); // minimal example app.listen(4000, () => console.log('Server running on 4000'));
App.jsx (client/src/App.jsx):
// client/src/App.jsx import React from 'react'; import BurgerList from './components/BurgerList'; export default function App() { return ( <div> <h1>React Burger Shop</h1> <BurgerList /> </div> ); }
BurgerList.jsx (client/src/components/BurgerList.jsx):
// client/src/components/BurgerList.jsx import React, { useEffect, useState } from 'react'; export default function BurgerList() { const [burgers, setBurgers] = useState([]); useEffect(() => { fetch('/api/burgers') .then(r => r.json()) .then(setBurgers); }, []); return ( <ul id="catalog"> {burgers.map(b => ( <li key={b.id}> <strong>{b.name}</strong> — ${b.price} <button onClick={() => addToCart(b.id)}>Add</button> </li> ))} </ul> ); } // addToCart calls API async function addToCart(id) { await fetch('/api/cart', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ burgerId: id, qty: 1 })}); }
Run React dev server (CRA / Vite) and proxy `/api` to the backend in dev for CORS convenience.
Examples:
// server/tests/burgers.test.js (supertest) const request = require('supertest'); const app = require('../src/index'); describe('GET /api/burgers', () => { it('responds with json', async () => { const res = await request(app).get('/api/burgers'); expect(res.status).toBe(200); expect(Array.isArray(res.body)).toBe(true); }); }); // client/src/components/BurgerList.test.jsx (React Testing Library) import render, screen from '@testing-library/react'; import BurgerList from './BurgerList'; test('renders list', () => { render(<BurgerList />); expect(screen.getByText(/React Burger Shop/i)).toBeInTheDocument(); });
Use Jest for unit + RTL for components; Supertest for API endpoints.
| Layer | Example | Purpose |
|---|---|---|
| Database | Postgres (Sequelize) | Store burgers, cart rows |
| Backend | Express routes | JSON API for CRUD |
| React Frontend | App.jsx, BurgerList.jsx | Render initial UI, call API |
| Testing | Jest / RTL / Supertest | Unit & integration tests |
| Dev flow | npm run dev / proxy | Local dev with hot reload |