MongoDB

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).
  • Перейдите в раздел 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. В конечном итоге проект выглядит следующим образом

Файл game.js

——————————————————————-

<!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 .