Мерч-магазин StudyCode: каталог, корзина, оформление заказа. Сервер сам считает сумму, проверяет остатки и ведёт статусы заказов — фронту доверять нельзя.
Команда StudyCode запускает магазин мерча: футболки «404 not found», кружки «git push --force», стикерпаки. Фронтенд уже свёрстан — витрина, корзина, кнопка «Оформить заказ». Не хватает одного: сервера, который скажет, что есть на складе, посчитает деньги и оформит заказ. Его напишешь ты.
Это первый проект, где сервер не просто хранит данные, а ведёт бизнес-логику: считает суммы, следит за складом и не даёт покупателю (и хакеру) себя обмануть.
products (склад) cart (корзина) orders (заказы)
┌─────────────────────┐ ┌────────────────┐ ┌───────────────────────┐
│ id 1 │◄─────│ productId 1 │ │ id 1 │
│ title "Футболка…" │ │ qty 2 │ │ items [снимок цен] │
│ price 1200 │ └────────────────┘ │ total 2400 │
│ stock 5 │ │ status "created" │
└─────────────────────┘ └───────────────────────┘Обрати внимание: в cart лежат только productId и qty. Ни названия, ни цены — они живут в products, корзина ссылается на них по productId. Так данные не дублируются: изменил цену в одном месте — корзина сразу считает по-новому.
Открой любой интернет-магазин, нажми F12 → вкладка Network → добавь товар в корзину. Ты увидишь запрос, который улетает на сервер. Плохая новость: этот запрос можно отредактировать — прямо в DevTools, за 10 секунд.
Если фронт присылает { productId: 1, price: 1, total: 1 }, а сервер верит — поздравляю, MacBook за 1 ₽. Поэтому:
productId и qtyproductscreated ────→ paid ────→ shipped
(создан) (оплачен) (отправлен)Заказ движется только вперёд и только на один шаг. Нельзя отправить неоплаченный заказ, нельзя «разоплатить» отправленный. На шаге 7 ты оформишь это как конечный автомат.
| Метод | URL | Что делает | Ответы |
|---|---|---|---|
| GET | /api/products | Каталог, ?inStock=1 — только в наличии | 200 |
| POST | /api/cart | Добавить в корзину {productId, qty} | 201 · 400 · 404 |
| GET | /api/cart | Корзина с ценами и итогом | 200 |
| DELETE | /api/cart/:productId | Убрать позицию | 200 · 404 |
| POST | /api/orders | Оформить заказ, списать склад | 201 · 400 · 409 |
| GET | /api/orders | Список заказов | 200 |
| PATCH | /api/orders/:id/status | Сменить статус заказа | 200 · 400 · 404 |
Корзина — это «хотелка», она может лежать часами. Пока ты думал — другой покупатель забрал последнюю футболку. Проверка при добавлении в корзину устаревает через секунду и даёт ложную уверенность. Поэтому:
Так делают почти все магазины — вспомни сообщение «пока вы оформляли, товар закончился» 🙂
const app = express();
app.use(express.json());
// Склад
let products = [
{ id: 1, title: 'Футболка «404 not found»', price: 1200, stock: 5 },
{ id: 2, title: 'Кружка «git push --force»', price: 700, stock: 3 },
{ id: 3, title: 'Стикерпак StudyCode', price: 300, stock: 0 },
];
let cart = []; // { productId, qty } — только ссылка на товар и количество
let orders = []; // оформленные заказы
let nextOrderId = 1;
// Шаг 2: GET /api/products — каталог (+ фильтр ?inStock=1)
// Шаг 3: POST /api/cart — добавить в корзину
// Шаг 4: GET /api/cart — корзина с ценами и итогом
// Шаг 5: DELETE /api/cart/:productId — убрать позицию
// Шаг 6: POST /api/orders — оформить заказ, списать склад
// Шаг 7: GET /api/orders + PATCH /api/orders/:id/status
app.listen(3000, () => console.log('✅ http://localhost:3000'));Оформление заказа и жизненный цикл статуса — нажми на узел.
Клиент нажал «Оформить». Сервер работает со своей корзиной cart — фронт не присылает ни цен, ни сумм.