Тема, Мета, Місце розташування
Тема: «СТВОРЕННЯ БАЗИ ДАНИХ У MYSQL. ПІДКЛЮЧЕННЯ NODE.JS ДО MYSQL. РОБОТА З ORM SEQUELIZE.»
Мета:
- Навчитися створювати базу даних у MySQL.
- Освоїти виконання SQL-запитів (SELECT, INSERT, UPDATE, DELETE).
- Підключати серверну програму на Node.js до бази даних.
- Використовувати ORM Sequelize для роботи з БД.
- Реалізувати зв’язок One-to-Many між таблицями.
Місце розташування:
- GitHub: https://github.com/valeritem/MiniBlog.git
- Live demo: https://mini-blog-client.onrender.com
Завдання
- Створити базу даних MongoDB, яка буде використовуватись власним веб-застосунком (наприклад,
mern-blog). - Створити колекції (аналог таблиць у реляційних БД) у базі даних (наприклад,
users,posts,comments). - Виконати базові CRUD-запити (аналог SQL-запитів) на рівні бази даних:
find()(Читання / аналог SELECT)insertOne()/insertMany()(Створення / аналог INSERT)updateOne()/updateMany()(Оновлення / аналог UPDATE)deleteOne()/deleteMany()(Видалення / аналог DELETE)
- Підключити Node.js до MongoDB за допомогою відповідних пакетів (наприклад,
mongoose). - Виконати CRUD-запити з Node.js до бази даних (реалізувати контролери для створення, отримання, редагування та видалення даних).
- Використати ODM Mongoose (Об’єктно-Документне Відображення — NoSQL-аналог ORM) для роботи з даними через JavaScript-об’єкти.
- Створити Mongoose-схеми та моделі для основних сутностей проєкту:
User,Post(та додатковоComment). - Реалізувати зв’язок One-to-Many (Один-до-багатьох). Наприклад: один користувач має багато статей, а одна стаття містить багато коментарів. Зв’язок реалізувати через збереження масиву ідентифікаторів (
mongoose.Schema.Types.ObjectId) із властивістюref.
Реалізація таблиці БД
Створення таблиці відбувалось за раніше побудованною ER моделлю. 
Реалізація серверної частини та роботи з БД
Структура проєкту (Backend)
Проєкт організований за принципом:
config/config.js— конфігурація та змінні середовища для підключення до бази даних MongoDB.
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const env = process.env.NODE_ENV || 'development';
dotenv.config({
path:
env === 'test' ? path.join(__dirname, '../tests/.env.test') : path.join(__dirname, '../.env'),
});
export default {
env,
port: process.env.PORT || 4444,
mongoUrl: process.env.MONGO_URL,
jwtSecret: process.env.JWT_SECRET,
}; models/— опис Mongoose-схем та моделей:User.js
import mongoose from 'mongoose';
const UserSchema = new mongoose.Schema(
{
username: {
type: String,
required: true,
unigue: true,
},
password: {
type: String,
required: true,
},
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post',
},
],
},
{ timestamps: true }
);
export default mongoose.model('User', UserSchema); Post.js
import mongoose from 'mongoose';
const PostSchema = new mongoose.Schema(
{
username: { type: String },
title: { type: String, required: true },
text: { type: String, required: true },
imgUrl: { type: String, default: '' },
views: { type: Number, default: 0 },
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }],
},
{ timestamps: true }
);
export default mongoose.model('Post', PostSchema); Comment.js
import mongoose from 'mongoose';
const CommentSchema = new mongoose.Schema(
{
comment: { type: String, required: true },
post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' },
},
{ timestamps: true }
);
export default mongoose.model('Comment', CommentSchema); index.js(таapp.js) — основний файл сервера, налаштування middleware, підключення до бази даних (Mongoose connect) та ініціалізація маршрутів.
controllers/таroutes/— контролери (auth.js,posts.js,comments.js) та маршрути, що інкапсулюють бізнес-логіку. Саме тут реалізовані всі CRUD-операції, які обробляють запити до бази даних.server\controllers\auth.js
import User from '../models/User.js';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
// Register user
export const register = async (req, res) => {
try {
const { username, password } = req.body;
const isUsed = await User.findOne({ username });
if (isUsed) {
return res.status(409).json({
message: 'Даний username вже зайнятий. ',
});
}
const salt = bcrypt.genSaltSync(10);
const hash = bcrypt.hashSync(password, salt);
const newUser = new User({
username,
password: hash,
});
await newUser.save();
const token = jwt.sign(
{
id: newUser._id,
},
process.env.JWT_SECRET,
{ expiresIn: '30d' }
);
res.status(200).json({
newUser,
token,
message: 'Реєстрація пройшла успішно.',
});
} catch (error) {
res.status(500).json({ message: 'Помилка при створені користувача' });
}
};
// Login user
export const login = async (req, res) => {
try {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user) {
return res.status(404).json({
message: 'Такого корситувача не існує.',
});
}
const isPasswordCorrect = await bcrypt.compare(password, user.password);
if (!isPasswordCorrect) {
return res.status(400).json({
message: 'Неправильний пароль.',
});
}
const token = jwt.sign(
{
id: user._id,
},
process.env.JWT_SECRET,
{ expiresIn: '30d' }
);
res.json({
token,
user,
message: 'Ви увійшли в систему.',
});
} catch (error) {
res.status(500).json({ message: 'Помилка при авторизації' });
}
};
// Get me
export const getMe = async (req, res) => {
try {
const user = await User.findById(req.userId);
if (!user) {
return res.json({
message: 'Такого корситувача не існує.',
});
}
const token = jwt.sign(
{
id: user._id,
},
process.env.JWT_SECRET,
{ expiresIn: '30d' }
);
res.json({
token,
user,
});
} catch (error) {
res.json({ message: 'Немає доступу.' });
}
}; server\controllers\comments.js
import Comment from '../models/Comment.js';
import Post from '../models/Post.js';
export const createComment = async (req, res) => {
try {
//const { postId, comment } = req.body;
const { comment } = req.body;
const postId = req.params.id;
if (!comment) return res.json({ messege: 'Коментар не може бути пустим' });
const newComment = new Comment({ comment });
await newComment.save();
try {
await Post.findByIdAndUpdate(postId, {
$push: { comments: newComment._id },
});
} catch (error) {
console.log(error);
}
res.status(201).json(newComment);
} catch (error) {
res.json({ messege: 'Щось пішло не так.' });
}
}; Створені колекції моделей User.js, Post.js, Comment.js (виведення через MongoDB Compass або MongoDB Atlas)
User model (Модель користувача)

Post model (Модель статті)

Comment model (Модель коментаря)

Реалізація Зв’язку One-to-Many
У MongoDB (за допомогою Mongoose) зв’язки описуються безпосередньо в самих схемах (використовуючи mongoose.Schema.Types.ObjectId та ref), а не в окремому файлі.
Зв’язок налаштовано між:
UserтаPost(Один користувач має багато статей). У моделі User масив posts зберігає ідентифікатори всіх статей, створених користувачем. Водночас у моделі Post поле author вказує на конкретного користувача-автора.
// ...
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post', // Посилання на модель Post
},
],
// ... // ...
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User' // Посилання на модель User (автор статті)
},
// ... PostтаComment(Одна стаття має багато коментарів). У моделі Post поле comments є масивом, який зберігає ObjectId всіх залишених під статтею коментарів.
// ...
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment' // Посилання на модель Comment
}
],
// ... UserтаComment(Один користувач може написати багато коментарів). У моделі Comment вказується посилання на користувача, який залишив цей коментар.
// ...
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User' // Посилання на модель User (автор коментаря)
},
// ... Програмний код сценаріїв виконання запитів (CRUD)
Замість окремого тестового скрипта, операції створення (Insert), читання (Select), оновлення (Update) та видалення (Delete) інтегровані безпосередньо в API контролери (наприклад, у controllers/posts.js).
Нижче наведено фрагменти коду контролерів, що реалізують демонстрацію операцій згідно з завданням.
import { PostService } from 'blog-core';
import { MongoosePostRepository } from '../repositories/MongoosePostRepository.js';
import { LocalFileStorage } from '../services/LocalFileStorage.js';
const postRepository = new MongoosePostRepository();
const fileStorage = new LocalFileStorage();
const postService = new PostService(postRepository, fileStorage);
// Create Post
export const createPost = async (req, res) => {
try {
const { title, text } = req.body;
const createPostDTO = {
title,
text,
authorId: req.userId,
username: req.body.username,
};
if (req.files && req.files.image) {
createPostDTO.image = {
name: req.files.image.name,
data: req.files.image.data,
};
}
const newPost = await postService.createPost(createPostDTO);
res.status(200).json(newPost);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Щось пішло не так при створенні поста.' });
}
};
// Get All Posts
export const getAll = async (req, res) => {
try {
const result = await postService.getAllPosts();
res.json(result);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Щось пішло не так.' });
}
};
// Get Post By Id
export const getById = async (req, res) => {
try {
const post = await postService.getPostById(req.params.id);
if (!post) {
return res.status(404).json({ message: 'Пост не знайдено.' });
}
res.json(post);
} catch (error) {
res.status(500).json({ message: 'Щось пішло не так.' });
}
};
// Get My Posts
export const getMyPosts = async (req, res) => {
try {
const posts = await postService.getMyPosts(req.userId);
res.json(posts);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Щось пішло не так.' });
}
};
// Remove Post
export const removePost = async (req, res) => {
try {
const success = await postService.removePost(req.params.id);
if (!success) {
return res.status(404).json({ message: 'Такого поста не існує.' });
}
res.json({ message: 'Пост видалено.' });
} catch (error) {
res.status(500).json({ message: 'Щось пішло не так.' });
}
};
// Update Post
export const updatePost = async (req, res) => {
try {
const { title, text, id } = req.body;
const updateDTO = { title, text };
if (req.files && req.files.image) {
updateDTO.image = {
name: req.files.image.name,
data: req.files.image.data,
};
}
const updatedPost = await postService.updatePost(id, updateDTO);
if (!updatedPost) {
return res.status(404).json({ message: 'Пост не знайдено.' });
}
res.json(updatedPost);
} catch (error) {
res.status(500).json({ message: 'Щось пішло не так.' });
}
};
// Get Post Comments
import Post from '../models/Post.js';
import Comment from '../models/Comment.js';
export const getPostComments = async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ message: 'Пост не знайдено' });
}
const list = await Promise.all(
post.comments.map((comment) => {
return Comment.findById(comment);
})
);
res.json(list);
} catch (error) {
res.json({ message: 'Щось пішло не так.' });
}
};
