Ithy Logo

Создание базы данных и Backend для веб-сайта с товарами и услугами

Полное руководство по разработке решения с использованием PostgreSQL и Node.js

e commerce database setup

Ключевые выводы

  • Надежное хранение данных с использованием PostgreSQL обеспечивает устойчивость и масштабируемость.
  • Аутентификация пользователей реализована с помощью JSON Web Tokens (JWT) для безопасного доступа.
  • Гибкая система заказов позволяет пользователям заказывать как товары, так и услуги через единый интерфейс.

1. Проектирование структуры базы данных

1.1. Основные таблицы

Для эффективного управления данными веб-сайта, предоставляющего товары и услуги, необходимо разработать структуру базы данных, включающую следующие таблицы:

Таблица users (Пользователи)

Хранит информацию о зарегистрированных пользователях системы.

Поле Тип Описание
id SERIAL PRIMARY KEY Уникальный идентификатор пользователя
username VARCHAR(50) UNIQUE NOT NULL Уникальное имя пользователя
email VARCHAR(100) UNIQUE NOT NULL Уникальный адрес электронной почты
password VARCHAR(255) NOT NULL Хэшированный пароль пользователя
role VARCHAR(50) DEFAULT 'user' Роль пользователя (например, "user", "admin")
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Дата и время регистрации пользователя

Таблица categories (Категории)

Содержит основные категории и подкатегории товаров и услуг.

Поле Тип Описание
id SERIAL PRIMARY KEY Уникальный идентификатор категории
name VARCHAR(255) NOT NULL Название категории
parent_id INTEGER REFERENCES categories(id) Идентификатор родительской категории (если есть)
type VARCHAR(50) NOT NULL Тип категории ("product" или "service")

Таблица products (Товары)

Содержит информацию о товарах, предлагаемых на сайте.

Поле Тип Описание
id SERIAL PRIMARY KEY Уникальный идентификатор товара
name VARCHAR(255) NOT NULL Название товара
description TEXT Описание товара
price DECIMAL(10,2) NOT NULL Цена товара
category_id INTEGER REFERENCES categories(id) Идентификатор категории товара
image_url VARCHAR(255) URL изображения товара
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Дата добавления товара

Таблица services (Услуги)

Содержит информацию об услугах, предлагаемых на сайте.

Поле Тип Описание
id SERIAL PRIMARY KEY Уникальный идентификатор услуги
name VARCHAR(255) NOT NULL Название услуги
description TEXT Описание услуги
price DECIMAL(10,2) Цена услуги (если применимо)
category_id INTEGER REFERENCES categories(id) Идентификатор категории услуги
image_url VARCHAR(255) URL изображения услуги
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Дата добавления услуги

Таблица projects (Примеры проектов)

Содержит примеры реализованных проектов для каждой услуги, включая фотографии и комментарии.

Поле Тип Описание
id SERIAL PRIMARY KEY Уникальный идентификатор проекта
service_id INTEGER REFERENCES services(id) Идентификатор услуги, к которой относится проект
name VARCHAR(255) NOT NULL Название проекта
description TEXT Описание проекта
image_url VARCHAR(255) URL изображения проекта
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Дата добавления проекта

Таблица comments (Комментарии)

Позволяет пользователям оставлять комментарии к проектам.

Поле Тип Описание
id SERIAL PRIMARY KEY Уникальный идентификатор комментария
project_id INTEGER REFERENCES projects(id) Идентификатор проекта, к которому относится комментарий
user_id INTEGER REFERENCES users(id) Идентификатор пользователя, оставившего комментарий
comment TEXT NOT NULL Текст комментария
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Дата добавления комментария

Таблица orders (Заказы)

Хранит информацию о заказах, оформленных пользователями на товары и услуги.

Поле Тип Описание
id SERIAL PRIMARY KEY Уникальный идентификатор заказа
user_id INTEGER REFERENCES users(id) Идентификатор пользователя, оформившего заказ
total_price DECIMAL(10,2) NOT NULL Общая стоимость заказа
status VARCHAR(50) DEFAULT 'pending' Статус заказа (например, "pending", "completed")
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Дата и время создания заказа

Таблица order_items (Элементы заказа)

Содержит детали по каждому элементу заказа, будь то товар или услуга.

Поле Тип Описание
id SERIAL PRIMARY KEY Уникальный идентификатор элемента заказа
order_id INTEGER REFERENCES orders(id) Идентификатор заказа
product_id INTEGER REFERENCES products(id) Идентификатор товара (если применимо)
service_id INTEGER REFERENCES services(id) Идентификатор услуги (если применимо)
quantity INTEGER NOT NULL Количество товаров или услуг
price DECIMAL(10,2) NOT NULL Цена за единицу

2. Пример SQL-запросов для создания таблиц

Ниже приведены примеры SQL-запросов для создания вышеописанных таблиц в PostgreSQL:

    
-- Таблица пользователей
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    role VARCHAR(50) DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Таблица категорий
CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    parent_id INTEGER REFERENCES categories(id),
    type VARCHAR(50) NOT NULL
);

-- Таблица товаров
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10,2) NOT NULL,
    category_id INTEGER REFERENCES categories(id),
    image_url VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Таблица услуг
CREATE TABLE services (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10,2),
    category_id INTEGER REFERENCES categories(id),
    image_url VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Таблица проектов
CREATE TABLE projects (
    id SERIAL PRIMARY KEY,
    service_id INTEGER REFERENCES services(id),
    name VARCHAR(255) NOT NULL,
    description TEXT,
    image_url VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Таблица комментариев
CREATE TABLE comments (
    id SERIAL PRIMARY KEY,
    project_id INTEGER REFERENCES projects(id),
    user_id INTEGER REFERENCES users(id),
    comment TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Таблица заказов
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    total_price DECIMAL(10,2) NOT NULL,
    status VARCHAR(50) DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Таблица элементов заказа
CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES orders(id),
    product_id INTEGER REFERENCES products(id),
    service_id INTEGER REFERENCES services(id),
    quantity INTEGER NOT NULL,
    price DECIMAL(10,2) NOT NULL
);
    
  

3. Настройка Backend на Node.js

3.1. Установка необходимых зависимостей

Для разработки backend необходимо установить Node.js и следующие пакеты с помощью npm:

    
npm install express pg bcryptjs jsonwebtoken dotenv body-parser cors helmet
    
  

3.2. Структура проекта

Рекомендуемая структура проекта:

    
/backend
    /config
        db.js
    /controllers
        authController.js
        productController.js
        serviceController.js
        orderController.js
    /models
        User.js
        Product.js
        Service.js
        Order.js
    /routes
        authRoutes.js
        productRoutes.js
        serviceRoutes.js
        orderRoutes.js
    app.js
    .env
    
  

3.3. Конфигурация базы данных (PostgreSQL)

Создайте файл .env в корне проекта и добавьте следующие переменные окружения:

    
DB_HOST=localhost
DB_PORT=5432
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_NAME=your_db_name
SECRET_KEY=your_secret_key
    
  

3.4. Подключение к базе данных

Создайте файл db.js в папке config для подключения к PostgreSQL:

    
// config/db.js
const { Pool } = require('pg');
const dotenv = require('dotenv');

dotenv.config();

const pool = new Pool({
    user: process.env.DB_USER,
    host: process.env.DB_HOST,
    database: process.env.DB_NAME,
    password: process.env.DB_PASSWORD,
    port: process.env.DB_PORT,
});

module.exports = pool;
    
  

3.5. Создание моделей

Пример модели пользователя:

    
// models/User.js
const pool = require('../config/db');

const createUser = async (username, email, password, role) => {
    const result = await pool.query(
        'INSERT INTO users (username, email, password, role) VALUES ($1, $2, $3, $4) RETURNING *',
        [username, email, password, role]
    );
    return result.rows[0];
};

const getUserByEmail = async (email) => {
    const result = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
    return result.rows[0];
};

module.exports = { createUser, getUserByEmail };
    
  

3.6. Реализация контроллеров

Пример контроллера аутентификации:

    
// controllers/authController.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');

const register = async (req, res) => {
    const { username, email, password } = req.body;
    try {
        const existingUser = await User.getUserByEmail(email);
        if (existingUser) {
            return res.status(400).json({ message: 'Пользователь с таким email уже существует' });
        }
        const hashedPassword = await bcrypt.hash(password, 10);
        const newUser = await User.createUser(username, email, hashedPassword, 'user');
        res.status(201).json(newUser);
    } catch (error) {
        res.status(500).json({ message: 'Ошибка сервера' });
    }
};

const login = async (req, res) => {
    const { email, password } = req.body;
    try {
        const user = await User.getUserByEmail(email);
        if (!user) {
            return res.status(401).json({ message: 'Неверный email или пароль' });
        }
        const isMatch = await bcrypt.compare(password, user.password);
        if (!isMatch) {
            return res.status(401).json({ message: 'Неверный email или пароль' });
        }
        const token = jwt.sign({ id: user.id, role: user.role }, process.env.SECRET_KEY, { expiresIn: '1h' });
        res.status(200).json({ token });
    } catch (error) {
        res.status(500).json({ message: 'Ошибка сервера' });
    }
};

module.exports = { register, login };
    
  

3.7. Настройка маршрутов

Пример маршрутов аутентификации:

    
// routes/authRoutes.js
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');

router.post('/register', authController.register);
router.post('/login', authController.login);

module.exports = router;
    
  

3.8. Главный файл приложения

Конфигурация и запуск сервера:

    
// app.js
const express = require('express');
const bodyParser = require('body-parser');
const dotenv = require('dotenv');
const cors = require('cors');
const helmet = require('helmet');

const authRoutes = require('./routes/authRoutes');
const productRoutes = require('./routes/productRoutes');
const serviceRoutes = require('./routes/serviceRoutes');
const orderRoutes = require('./routes/orderRoutes');

dotenv.config();

const app = express();

app.use(helmet());
app.use(cors());
app.use(bodyParser.json());

app.use('/api/auth', authRoutes);
app.use('/api/products', productRoutes);
app.use('/api/services', serviceRoutes);
app.use('/api/orders', orderRoutes);

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Сервер запущен на порту ${PORT}`));
    
  

4. Аутентификация и безопасность

4.1. Хэширование паролей

Использование библиотеки bcryptjs для надежного хэширования паролей пользователей:

    
const bcrypt = require('bcryptjs');

const hashPassword = async (password) => {
    const salt = await bcrypt.genSalt(10);
    return await bcrypt.hash(password, salt);
};

const comparePassword = async (password, hashedPassword) => {
    return await bcrypt.compare(password, hashedPassword);
};
    
  

4.2. Использование JWT для авторизации

JSON Web Tokens (JWT) обеспечивают безопасную передачу информации между клиентом и сервером:

    
// Генерация токена
const token = jwt.sign({ id: user.id, role: user.role }, process.env.SECRET_KEY, { expiresIn: '1h' });

// Верификация токена
const authenticate = (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) return res.status(401).json({ message: 'Нет токена, авторизуйтесь' });

    try {
        const decoded = jwt.verify(token, process.env.SECRET_KEY);
        req.user = decoded;
        next();
    } catch (error) {
        res.status(401).json({ message: 'Токен недействителен' });
    }
};
    
  

4.3. Защита маршрутов

Применение middleware для защиты маршрутов, доступных только авторизованным пользователям:

    
// Пример защищенного маршрута
const express = require('express');
const router = express.Router();
const { authenticate } = require('../middleware/authenticate');
const orderController = require('../controllers/orderController');

router.post('/', authenticate, orderController.createOrder);
router.get('/:id', authenticate, orderController.getOrderById);

module.exports = router;
    
  

5. Реализация системы заказов

5.1. Контроллер заказов

Пример контроллера для создания заказа:

    
// controllers/orderController.js
const pool = require('../config/db');

const createOrder = async (req, res) => {
    const { items } = req.body; // items: массив объектов с product_id/service_id и quantity
    const userId = req.user.id;
    let totalPrice = 0;

    try {
        // Начало транзакции
        await pool.query('BEGIN');

        // Расчет общей стоимости
        for (const item of items) {
            if (item.product_id) {
                const product = await pool.query('SELECT price FROM products WHERE id = $1', [item.product_id]);
                if (product.rows.length === 0) throw new Error('Товар не найден');
                totalPrice += parseFloat(product.rows[0].price) * item.quantity;
            }
            if (item.service_id) {
                const service = await pool.query('SELECT price FROM services WHERE id = $1', [item.service_id]);
                if (service.rows.length === 0) throw new Error('Услуга не найдена');
                totalPrice += parseFloat(service.rows[0].price) * item.quantity;
            }
        }

        // Создание заказа
        const order = await pool.query(
            'INSERT INTO orders (user_id, total_price, status) VALUES ($1, $2, $3) RETURNING *',
            [userId, totalPrice, 'pending']
        );

        const orderId = order.rows[0].id;

        // Добавление элементов заказа
        for (const item of items) {
            await pool.query(
                'INSERT INTO order_items (order_id, product_id, service_id, quantity, price) VALUES ($1, $2, $3, $4, $5)',
                [
                    orderId,
                    item.product_id || null,
                    item.service_id || null,
                    item.quantity,
                    item.price
                ]
            );
        }

        // Коммит транзакции
        await pool.query('COMMIT');
        res.status(201).json({ orderId, totalPrice });
    } catch (error) {
        // Откат транзакции в случае ошибки
        await pool.query('ROLLBACK');
        res.status(500).json({ message: error.message });
    }
};

const getOrderById = async (req, res) => {
    const orderId = req.params.id;
    const userId = req.user.id;

    try {
        const order = await pool.query('SELECT * FROM orders WHERE id = $1 AND user_id = $2', [orderId, userId]);
        if (order.rows.length === 0) return res.status(404).json({ message: 'Заказ не найден' });
        res.status(200).json(order.rows[0]);
    } catch (error) {
        res.status(500).json({ message: 'Ошибка сервера' });
    }
};

module.exports = { createOrder, getOrderById };
    
  

5.2. Маршруты заказов

Пример маршрутов для управления заказами:

    
// routes/orderRoutes.js
const express = require('express');
const router = express.Router();
const orderController = require('../controllers/orderController');
const { authenticate } = require('../middleware/authenticate');

router.post('/', authenticate, orderController.createOrder);
router.get('/:id', authenticate, orderController.getOrderById);

module.exports = router;
    
  

6. Масштабирование и оптимизация

6.1. Оптимизация запросов

Для повышения производительности базы данных рекомендуется использовать индексы:

    
-- Создание индекса для быстрого поиска по email
CREATE INDEX idx_users_email ON users(email);

-- Индекс для поиска товаров по категории
CREATE INDEX idx_products_category_id ON products(category_id);
    
  

6.2. Добавление middleware для безопасности

Использование библиотек helmet и cors для обеспечения безопасности приложения:

    
// app.js
const helmet = require('helmet');
const cors = require('cors');

app.use(helmet());
app.use(cors());
    
  

6.3. Разделение проекта на модули

Для упрощения поддержки и масштабирования проекта рекомендуется разделить код на отдельные модули: контроллеры, модели, маршруты и т.д.

6.4. Использование кластеризации

Для повышения производительности и надежности приложения можно использовать кластеризацию Node.js с помощью модуля cluster.

    
// server.js
const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
    const numCPUs = os.cpus().length;
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }
    cluster.on('exit', (worker, code, signal) => {
        console.log(`Рабочий ${worker.process.pid} завершился`);
        cluster.fork();
    });
} else {
    require('./app');
}
    
  

7. Заключение

Данное руководство предоставляет подробную информацию о создании базы данных и настройке backend для веб-сайта, предлагающего товары и услуги. Использование PostgreSQL обеспечивает надежное хранение данных, а Node.js с использованием Express позволяет эффективно реализовать функционал аутентификации, управления товарами, услугами и заказами. Следуя предложенной структуре и рекомендациям по безопасности и масштабированию, вы сможете разработать стабильное и масштабируемое веб-приложение.

Ссылки на источники


Last updated January 20, 2025
Ask me more