PIvotal Tracker


1. Регистрация и настройка MongoDB Atlas
1.1 Регистрация на MongoDB Atlas
- Перейдите на MongoDB Atlas и зарегистрируйтесь.
- Создайте новый кластер:
- Выберите бесплатный план (Shared Cluster).
- Установите настройки региона кластера в зависимости от вашего местоположения.
1.2 Создание базы данных и коллекции
- В панели управления Atlas выберите созданный кластер.
- Перейдите в раздел Database Access и добавьте нового пользователя:
- Установите имя пользователя и пароль.
- Выберите права доступа: Read and Write to any database.
- В разделе Network Access добавьте IP-адрес:
- Для локальной разработки можно добавить доступ с любого IP (
0.0.0.0/0
).
- Для локальной разработки можно добавить доступ с любого IP (
- Перейдите в раздел Database -> Browse Collections:
- Создайте новую базу данных, например,
games-api
, и первую коллекцию, например,games
.
- Создайте новую базу данных, например,
1.3 Получение строки подключения
- В разделе Clusters нажмите кнопку Connect для вашего кластера.
- Выберите Connect your application.
- Скопируйте строку подключения вида:
const uri = "mongodb+srv://martin:martinpassword@cluster0.ojj0f.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0";
2. Подключение MongoDB в проекте с использованием Mongoose
2.1 Установка Mongoose
- Установите Mongoose через npm:
npm install mongoose
2.2 Создание подключения к MongoDB
- В основном серверном файле (
index.js
), подключите Mongoose к вашему кластеру MongoDB Atlas.
const uri = "mongodb+srv://martin:martinpassword@cluster0.ojj0f.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0";
mongoose.connect(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('Connected to MongoDB Atlas'))
.catch((error) => console.error('Error connecting to MongoDB Atlas:', error));
- Замените
<password>
на ваши значения из строки подключения, которую вы получили в MongoDB Atlas.
3. Создание модели с помощью Mongoose
3.1 Создание схемы для модели
- Создайте папку
models
в проекте. - Внутри папки
models
создайте файлgame.js
.

Пример схемы Mongoose:
const mongoose = require('mongoose'); // Определение схемы для игр
const gameSchema = newmongoose.Schema({ id: { type: Number, required: true, unique: true }, name: { type: String, required: true }, price: { type: Number, required: true }}); // Создание модели на основе схемы
module.exports = mongoose.model('Game', gameSchema);
4. Создание API для работы с данными MongoDB
4.1 Маршрут для получения всех записей
В основном серверном файле добавьте маршрут для получения всех игр:
// Маршрут для получения списка игр
app.get('/games', async (req, res) => {
try {
const games = await Game.find();
res.json(games);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
4.2 Маршрут для добавления новой игры
Добавьте возможность создавать новые записи:
app.post('/games', async (req, res) => {
try {
const lastGame = await Game.findOne().sort({ id: -1 });
const newId = lastGame ? lastGame.id + 1 : 1;
const game = new Game({
id: newId,
name: req.body.name,
price: req.body.price
});
const newGame = await game.save();
res.status(201).json(newGame);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
4.3 Маршрут для обновления записи
Для обновления записи используйте следующий маршрут:
// Маршрут для обновления игры
app.put('/games/:id', async (req, res) => {
try {
const game = await Game.findOne({ id: req.params.id });
if (!game) return res.status(404).json({ message: 'Game not found' });
game.name = req.body.name || game.name;
game.price = req.body.price || game.price;
const updatedGame = await game.save();
res.json(updatedGame);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
4.4 Маршрут для удаления записи
Для удаления записи по id
:
// Маршрут для удаления игры
app.delete('/games/:id', async (req, res) => {
try {
const game = await Game.findOne({ id: req.params.id });
if (!game) return res.status(404).json({ message: 'Game not found' });
await game.deleteOne();
res.json({ message: 'Game deleted' });
} catch (error) {
res.status(500).json({ message: error.message });
}
});
5. Делаем правильную структуру проекта
Переносим index.html и rest-client.js в созданную нами папку public

6. Меняем rest-client.js
const app = Vue.createApp({
data() {
return {
games: [],
gameInModal: { id: null, name: '', price: 0 },
newGame: { name: '', price: 0 }
};
},
methods: {
loadGames() {
fetch('/games')
.then((response) => response.json())
.then((games) => {
this.games = games;
});
},
openNewGameModal() {
this.newGame = { name: '', price: 0 };
document.getElementById('newGameModal').style.display = 'block';
},
insertNewGame() {
fetch('/games', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.newGame)
}).then(() => {
document.getElementById('newGameModal').style.display = 'none';
this.loadGames();
});
},
editGame(game) {
this.gameInModal = { ...game };
document.getElementById('gameInfoModal').style.display = 'block';
},
updateGame() {
fetch(`/games/${this.gameInModal.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.gameInModal)
}).then(() => {
document.getElementById('gameInfoModal').style.display = 'none';
this.loadGames();
});
},
deleteGame(id) {
fetch(`/games/${id}`, {
method: 'DELETE'
}).then(() => {
this.loadGames();
});
},
closeModal() {
document.getElementById('gameInfoModal').style.display = 'none';
document.getElementById('newGameModal').style.display = 'none';
}
},
mounted() {
this.loadGames();
}
}).mount('#app');
7. В конечном итоге проект выглядит следующим образом

——————————————————————-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Games List</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body class="d-flex flex-column min-vh-100">
<div id="app" class="container my-5">
<h1 id="main-title">List of Games</h1>
<table id="gamesTable" class="table table-hover table-striped table-bordered shadow-sm rounded">
<thead class="table-primary">
<tr>
<th>Name</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="game in games" :key="game.id">
<td @click="getGame(game.id)" class="text-primary" style="cursor:pointer;">{{ game.name }}</td>
<td>€{{ game.price }}</td>
<td>
<button class="btn btn-warning btn-sm action-btn" @click="editGame(game)">Edit</button>
<button class="btn btn-danger btn-sm action-btn" @click="deleteGame(game.id)">Delete</button>
</td>
</tr>
</tbody>
</table>
<button class="btn btn-success" @click="openNewGameModal">Insert New Game</button>
<div id="gameInfoModal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Game Info</h5>
<button type="button" class="btn-close" @click="closeModal"></button>
</div>
<div class="modal-body">
<table class="table table-striped">
<tr><th>ID</th><td>{{ gameInModal.id }}</td></tr>
<tr><th>Name</th><td><input v-model="gameInModal.name" type="text" class="form-control"></td></tr>
<tr><th>Price</th><td><input v-model="gameInModal.price" type="number" step="0.01" class="form-control"></td></tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="closeModal">Close</button>
<button type="button" class="btn btn-primary" @click="updateGame(gameInModal.id)">Save Changes</button>
</div>
</div>
</div>
</div>
<div id="newGameModal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Insert New Game</h5>
<button type="button" class="btn-close" @click="closeModal"></button>
</div>
<div class="modal-body">
<table class="table table-striped">
<tr><th>Name</th><td><input v-model="newGame.name" type="text" class="form-control"></td></tr>
<tr><th>Price</th><td><input v-model="newGame.price" type="number" step="0.01" class="form-control"></td></tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="closeModal">Close</button>
<button type="button" class="btn btn-success" @click="insertNewGame">Save</button>
</div>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script src="rest-client.js"></script>
</body>
</html>
Файл index.html
——————————————————————–
const app = Vue.createApp({
data() {
return {
games: [],
gameInModal: { id: null, name: '', price: 0 },
newGame: { name: '', price: 0 }
};
},
methods: {
loadGames() {
fetch('/games')
.then((response) => response.json())
.then((games) => {
this.games = games;
});
},
openNewGameModal() {
this.newGame = { name: '', price: 0 };
document.getElementById('newGameModal').style.display = 'block';
},
insertNewGame() {
fetch('/games', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.newGame)
}).then(() => {
document.getElementById('newGameModal').style.display = 'none';
this.loadGames();
});
},
editGame(game) {
this.gameInModal = { ...game };
document.getElementById('gameInfoModal').style.display = 'block';
},
updateGame() {
fetch(`/games/${this.gameInModal.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.gameInModal)
}).then(() => {
document.getElementById('gameInfoModal').style.display = 'none';
this.loadGames();
});
},
deleteGame(id) {
fetch(`/games/${id}`, {
method: 'DELETE'
}).then(() => {
this.loadGames();
});
},
closeModal() {
document.getElementById('gameInfoModal').style.display = 'none';
document.getElementById('newGameModal').style.display = 'none';
}
},
mounted() {
this.loadGames();
}
}).mount('#app');
Файл rest-client.js
——————————————————————–
const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const swaggerUi = require('swagger-ui-express');
const yamljs = require('yamljs');
const swaggerDocument = yamljs.load('./docs/swagger.yaml');
const Game = require('./models/game'); // Импорт модели игры
const app = express();
app.use(express.json()); // Middleware для работы с JSON
// Подключение к MongoDB Atlas
const uri = "mongodb+srv://martin:martinpassword@cluster0.ojj0f.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0";
mongoose.connect(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('Connected to MongoDB Atlas'))
.catch((error) => console.error('Error connecting to MongoDB Atlas:', error));
// Обслуживание статических файлов
app.use(express.static(path.join(__dirname, 'public')));
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
// Маршрут для получения списка игр
app.get('/games', async (req, res) => {
try {
const games = await Game.find();
res.json(games);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
// Маршрут для добавления новой игры
app.post('/games', async (req, res) => {
try {
const lastGame = await Game.findOne().sort({ id: -1 });
const newId = lastGame ? lastGame.id + 1 : 1;
const game = new Game({
id: newId,
name: req.body.name,
price: req.body.price
});
const newGame = await game.save();
res.status(201).json(newGame);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
// Маршрут для обновления игры
app.put('/games/:id', async (req, res) => {
try {
const game = await Game.findOne({ id: req.params.id });
if (!game) return res.status(404).json({ message: 'Game not found' });
game.name = req.body.name || game.name;
game.price = req.body.price || game.price;
const updatedGame = await game.save();
res.json(updatedGame);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
// Маршрут для удаления игры
app.delete('/games/:id', async (req, res) => {
try {
const game = await Game.findOne({ id: req.params.id });
if (!game) return res.status(404).json({ message: 'Game not found' });
await game.deleteOne();
res.json({ message: 'Game deleted' });
} catch (error) {
res.status(500).json({ message: error.message });
}
});
// Запуск сервера
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
console.log(`API documentation available at http://localhost:${port}/api-docs`); // URL for Swagger UI
});
Файл index.js
8. Запуск проекта
node .