Прежде чем начать, убедитесь, что у вас установлены Node и Create React App. И если вы планируете использовать локальную базу данных mongodb, убедитесь, что у вас есть и эта настройка.

Справочный материал

Рабочий процесс Gitflow
Подключение к MongoDB
Операции MongoDB CRUD
Атлас MongoDB
Mongoose
Express.js
EJS
React
React Router
Redux
Netlify
Vercel

Необходимые инструменты

Вы можете использовать любой редактор кода и терминальное приложение. Но для взаимодействия с HTTP API на бэкенде. Я предпочитаю приложение Postman.

Редактор кода: Visual Studio Code
Terminal: Hyper
Приложение для тестирования API: Postman

Контрольный список

Это шаги, которые мы будем выполнять

  1. Инициализировать проект с помощью рабочего процесса GIT (дополнительно настроить доску канбан для проекта)
  2. Настройте базу данных MongoDB (локальную или онлайн)
  3. Создайте внутренний сервер Node/Express, который подключается к базе данных с помощью CRUD-запросов.
  4. Создайте интерфейс с помощью EJS или React/Redux.
  • Шаблоны Ejs (HTML и CSS Grid/Flexbox)
  • React/Redux (стилизованные компоненты с использованием CSS Grid/Flexbox)
  1. Онлайн-развертывание на рабочем сервере (Netlify, Vercel, Heroku и т. д.)

Настройка проекта

Я буду создавать приложение для отслеживания аниме, которое я смотрю. Однако не стесняйтесь использовать любую тему, которую вы хотите.

Рабочий процесс GIT

Перейдите на GitHub и создайте новый репозиторий, затем создайте папку на локальном компьютере и cd в нее с помощью приложения терминала. Затем инициализируйте репо, как показано ниже.

На протяжении всего проекта вы должны передавать свою работу на GitHub и следовать рабочему процессу GIT.

echo "# anime-tracker" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/yourname/anime-tracker.git
git push -u origin master

Настроить базу данных MongoDB

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

Онлайн
https://www.mongodb.com/cloud/atlas
https://mlab.com/

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

mongodb+srv://<username>:<password>@cluster0-tyqyw.mongodb.net/<dbname>?retryWrites=true&w=majority

Местный

Убедитесь, что у вас локально установлены mongoDB и mongoDB compass.

Используйте приведенные ниже команды в своем терминале, чтобы создать локальную базу данных по вашему выбору.

mongo
show dbs;
use animes;
db.createCollection("series");

Для подключения к базе данных вы будете использовать строку подключения ниже

mongodb://127.0.0.1:27017/animes

Настройте структуру папок и установите зависимости

Откройте папку проекта в редакторе кода, создайте внутреннюю папку и установите зависимости.

touch .gitignore
mkdir backend
cd backend
npm init -y
npm i express nodemon ejs cors concurrently mongoose dotenv

Настройте структуру папок внутри внутренней папки

mkdir controllers
mkdir models
mkdir public
mkdir routes
mkdir src
mkdir src/pages
touch app.js
touch .gitignore

Добавьте node_modules .env и .DS_Store в файл .gitignore в корневой и внутренней папках.

Создайте сервер Node/Express, который подключается к базе данных.

Создайте файл .env в корневом каталоге вашего проекта. Добавьте переменные среды в новые строки в виде NAME=VALUE. Например:

DB_HOST="mongodb://127.0.0.1:27017/animes"
DB_USER="databaseuser"
DB_PASS="databasepassword"

Откройте файл app.js и добавьте приведенный ниже код.

Локальные базы данных MongoDB не требуют имени пользователя и пароля, только хост

const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const cors = require('cors');
require('dotenv').config();
const app = express();
app.use(cors());
app.set('view engine', 'ejs');
app.set('views', './src/pages');
app.use(express.urlencoded({ extended: false }));
app.use('/static', express.static(path.join(`${__dirname}/public`)));
app.get('/', (req, res) => res.send('Home Route'));
const port = process.env.PORT || 8080;
mongoose
    .connect(process.env.DB_HOST, {
        useCreateIndex: true,
        useUnifiedTopology: true,
        useNewUrlParser: true,
      useFindAndModify: false,
    })
    .then(() => {
        app.listen(port, () => console.log(`Server and Database running on ${port}, http://localhost:${port}`));
    })
    .catch((err) => {
        console.log(err);
    });

Откройте файл package.json и добавьте следующие сценарии запуска для запуска, разработки и серверов.

{
    "name": "backend",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "start": "node app.js",
        "dev": "nodemon app.js",
        "servers": "concurrently \"npm run dev\" \"cd ../frontend && npm run start\""
    },
    "keywords": [],
    "author": "Andrew Baisden",
    "license": "MIT",
    "dependencies": {
        "concurrently": "^5.2.0",
        "cors": "^2.8.5",
        "dotenv": "^8.2.0",
        "ejs": "^3.1.3",
        "express": "^4.17.1",
        "mongoose": "^5.9.24",
        "nodemon": "^2.0.4"
    }
}

Используйте команду npm run dev в окне терминала, и приложение должно быть запущено и работать, а также подключено к вашей базе данных mongodb.

Древовидная структура (скрытые файлы не отображаются)

├── README.md
└── бэкенд
├── app.js
├── контроллеры
├── модели
├── node_modules
├── package-lock.json
├── package.json
├── public
├── route
└── src
└─ ─ страницы

8 каталогов, 4 файла

Создайте контроллер и файлы маршрутов

Сначала создайте файл index.ejs в src/pages и добавьте html ниже

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Home</title>
    </head>
    <body>
        <h1>Home Page</h1>
    </body>
</html>

Затем создайте файл edit-anime.ejs в src/pages и добавьте html ниже

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Add Anime</title>
    </head>
    <body>
        <h1>Add Anime</h1>
        <form method="POST" action="/add-anime">
            <div>
                <label>Name</label>
                <input type="text" name="name" required />
            </div>
            <div>
                <label>Image</label>
                <input type="text" name="image" required />
            </div>
            <div>
                <label>Description</label>
                <input type="text" name="description" required />
            </div>
            <div>
                <button type="submit">Add Anime</button>
            </div>
        </form>
    </body>
</html>

Наконец, создайте файл anime.ejs в src/pages и добавьте приведенный ниже HTML-код.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Anime</title>
    </head>
    <body>
        <h1>Anime</h1>
    </body>
</html>

Затем создайте файл admin.js и поместите его в папку controllers.

exports.getIndex = (req, res) => {
    res.status(200).render('index');
};

Затем создайте файл admin.js и поместите его в папку routes

const express = require('express');
const adminController = require('../controllers/admin');
const router = express.Router();
router.get('/', adminController.getIndex);
module.exports = router;

Импортируйте файл маршрута администратора в ваш основной файл app.js в корневой папке и замените домашний маршрут новым маршрутом администратора.

const adminRoute = require('./routes/admin');
// Replace the code for the old route with the new route code
// Old Code
app.get('/', (req, res) => res.send('Home Route'));
// New Code
app.use('/', adminRoute);

Создайте схему мангуста

Создайте файл Anime.js в папке моделей, а затем скопируйте и вставьте приведенный ниже код в этот файл.

const mongoose = require('mongoose');
const AnimeSchema = mongoose.Schema({
    name: {
        type: String,
        required: true,
    },
    image: {
        type: String,
        required: true,
    },
    description: {
        type: String,
        required: true,
    },
});
module.exports = mongoose.model('series', AnimeSchema);

Создание CRUD-запросов

Далее мы создадим CRUD-запросы для взаимодействия с базой данных. Это также прекрасная возможность использовать приложение Postman для HTTP-запросов Dong для всех маршрутов. Это позволит вам отправлять данные POST и видеть маршруты GET без использования браузера. Это выходит за рамки этого руководства, однако его очень легко использовать, если вы посмотрите документацию.

Добавление данных в базу данных (Создать)

Мы создаем маршруты для страницы с формой добавления и почтовым маршрутом для добавления данных этой формы в базу данных.

Обновите файл admin.js в папке controllers с помощью приведенного ниже кода.

const Anime = require('../models/Anime');
exports.getIndex = (req, res) => {
    res.status(200).render('index');
};
exports.getAddAnime = (req, res) => {
    res.status(200).render('edit-anime');
};
exports.postAnime = (req, res) => {
    const { name, image, description } = req.body;
    const anime = new Anime({ name: name, image: image, description: description });
    anime.save();
    console.log('Anime Added to the database');
    res.status(201).redirect('/');
};

Обновите файл admin.js в папке routes с помощью приведенного ниже кода.

const express = require('express');
const adminController = require('../controllers/admin');
const router = express.Router();
router.get('/', adminController.getIndex);
router.get('/add-anime', adminController.getAddAnime);
router.post('/add-anime', adminController.postAnime);
module.exports = router;

Теперь, если вы перейдете на http://localhost:8080/add-anime и отправите некоторые данные формы, они должны быть добавлены в вашу базу данных. Если вы используете локальную базу данных mongodb, используйте приложение MongoDB Compass для проверки вашей базы данных, вам нужно будет обновить ее, чтобы увидеть новые записи. Если у вас есть онлайн-база данных, просто перейдите в свой кластер, чтобы просмотреть коллекции.

В качестве альтернативы используйте приложение Postman для отправки почтового запроса на маршрут http://localhost:8080/add-anime, как в примере ниже.

Чтение данных из базы данных (Чтение)

Теперь мы извлекаем данные из базы данных и отображаем их внутри наших страниц с помощью вызовов асинхронных функций. Мы будем использовать язык шаблонов .ejs для создания страниц, поэтому обратитесь к документации, если хотите понять код. Это в основном похоже на ванильный javascript, но с .ejs синтаксическими тегами шаблонов, поэтому его должно быть легко понять.

Обновите файл admin.js в папке controllers с помощью приведенного ниже кода.

const Anime = require('../models/Anime');
exports.getIndex = async (req, res) => {
    const anime = await Anime.find((data) => data);
    try {
        console.log(anime);
        res.status(200).render('index', { anime: anime });
    } catch (error) {
        console.log(error);
    }
};
exports.getAnime = async (req, res) => {
    const animeId = req.params.animeId;
    const anime = await Anime.findById(animeId, (anime) => anime);
    try {
        console.log(anime);
        res.status(200).render('anime', { anime: anime });
    } catch (error) {
        console.log(error);
    }
};
exports.getAddAnime = (req, res) => {
    res.status(200).render('edit-anime');
};
exports.postAnime = (req, res) => {
    const { name, image, description } = req.body;
    const anime = new Anime({ name: name, image: image, description: description });
    anime.save();
    console.log('Anime Added to the database');
    res.status(201).redirect('/');
};

Обновите файл admin.js в папке routes с помощью приведенного ниже кода.

const express = require('express');
const adminController = require('../controllers/admin');
const router = express.Router();
router.get('/', adminController.getIndex);
router.get('/add-anime', adminController.getAddAnime);
router.post('/add-anime', adminController.postAnime);
router.get('/:animeId', adminController.getAnime);
module.exports = router;

Обновите файл index.ejs в папке src/pages с помощью приведенного ниже кода.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Home</title>
    </head>
    <body>
        <h1>Home Page</h1>
        <main>
            <% anime.forEach(data => { %>
                <ul>
                    <li><h1><a href="/<%= data.id %>"><%= data.name %></a></h1></li>
                    <li><img src="<%= data.image %>" alt="<%= data.name %>" /></h1></li>
                    <li><p><%= data.description %></p></li>
                </ul>
            <% }) %>
        </main>
    </body>
</html>

Обновите файл anime.ejs в папке src/pages с помощью приведенного ниже кода.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Anime</title>
    </head>
    <body>
        <h1>Anime</h1>
        <main>
            <h1><%= anime.name %></h1>
            <img src="<%= anime.image %>" alt="<%= anime.name %>" />
            <p><%= anime.description %></p>
        </main>
    </body>
</html>

Теперь вы должны увидеть данные вашей базы данных, отображаемые на главной странице, и если вы нажмете на одну из ссылок, вы должны перейти на соответствующую страницу на основе ее идентификатора. Эти данные также записываются в консоль.

Удаление данных из базы данных (Delete)

Теперь мы создаем маршрут удаления для удаления элементов из базы данных.

Обновите файл admin.js в папке controllers с помощью приведенного ниже кода.

const Anime = require('../models/Anime');
exports.getIndex = async (req, res) => {
    const anime = await Anime.find((data) => data);
    try {
        console.log(anime);
        res.status(200).render('index', { anime: anime });
    } catch (error) {
        console.log(error);
    }
};
exports.getAnime = async (req, res) => {
    const animeId = req.params.animeId;
    const anime = await Anime.findById(animeId, (anime) => anime);
    try {
        console.log(anime);
        res.status(200).render('anime', { anime: anime });
    } catch (error) {
        console.log(error);
    }
};
exports.getAddAnime = (req, res) => {
    res.status(200).render('edit-anime');
};
exports.postAnime = (req, res) => {
    const { name, image, description } = req.body;
    const anime = new Anime({ name: name, image: image, description: description });
    anime.save();
    console.log('Anime Added to the database');
    res.status(201).redirect('/');
};
exports.postDelete = async (req, res) => {
    const animeId = req.body.animeId;
    const anime = await Anime.findByIdAndRemove(animeId, (data) => data);
    try {
        console.log(anime);
        console.log('Item Deleted');
        res.redirect('/');
    } catch (error) {
        console.log(error);
    }
};

Обновите файл admin.js в папке routes с помощью приведенного ниже кода.

const express = require('express');
const adminController = require('../controllers/admin');
const router = express.Router();
router.get('/', adminController.getIndex);
router.get('/add-anime', adminController.getAddAnime);
router.post('/add-anime', adminController.postAnime);
router.get('/:animeId', adminController.getAnime);
router.post('/delete', adminController.postDelete);
module.exports = router;

Обновите файл anime.ejs в папке src/pages с помощью приведенного ниже кода.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Anime</title>
    </head>
    <body>
        <h1>Anime</h1>
        <main>
            <h1><%= anime.name %></h1>
            <img src="<%= anime.image %>" alt="<%= anime.name %>" />
            <p><%= anime.description %></p>
            <div>
                <form method="POST" action="/delete">
                    <div>
                        <input type="hidden" value="<%= anime.id %>" name="animeId" />
                        <button>Delete</button>
                    </div>
                </form>
            </div>
        </main>
    </body>
</html>

Теперь, если вы перейдете на страницу элемента и затем нажмете кнопку «Удалить», вы сможете удалить его.

Обновление данных из базы данных (Update)

Теперь мы создаем маршруты для обновления каждого элемента в базе данных. Обновите файлы с кодом ниже.

Обновите файл admin.js в папке controllers с помощью приведенного ниже кода.

const Anime = require('../models/Anime');
exports.getIndex = async (req, res) => {
    const anime = await Anime.find((data) => data);
    try {
        console.log(anime);
        res.status(200).render('index', { anime: anime });
    } catch (error) {
        console.log(error);
    }
};
exports.getAnime = async (req, res) => {
    const animeId = req.params.animeId;
    const anime = await Anime.findById(animeId, (anime) => anime);
    try {
        console.log(anime);
        res.status(200).render('anime', { anime: anime });
    } catch (error) {
        console.log(error);
    }
};
exports.getAddAnime = (req, res) => {
    res.status(200).render('edit-anime', { editing: false });
};
exports.getEditAnime = async (req, res) => {
    const animeId = req.params.animeId;
    const editMode = req.query.edit;
    if (!editMode) {
        return res.redirect('/');
    }
    const anime = await Anime.findById(animeId);
    try {
        if (!animeId) {
            return res.redirect('/');
        }
        console.log(anime);
        res.status(200).render('edit-anime', { anime: anime, editing: editMode });
    } catch (error) {
        console.log(error);
    }
};
exports.postAnime = (req, res) => {
    const { name, image, description } = req.body;
    const anime = new Anime({ name: name, image: image, description: description });
    anime.save();
    console.log('Anime Added to the database');
    res.status(201).redirect('/');
};
exports.postEditAnime = (req, res) => {
    const animeId = req.body.animeId;
    const { name, image, description } = req.body;
    Anime.findById(animeId)
        .then((anime) => {
            anime.name = name;
            anime.image = image;
            anime.description = description;
            return anime.save();
        })
        .then(() => {
            console.log('Item Updated');
            res.status(201).redirect('/');
        })
        .catch((err) => {
            console.log(err);
        });
};
exports.postDelete = async (req, res) => {
    const animeId = req.body.animeId;
    const anime = await Anime.findByIdAndRemove(animeId, (data) => data);
    try {
        console.log(anime);
        console.log('Item Deleted');
        res.redirect('/');
    } catch (error) {
        console.log(error);
    }
};

Обновите файл admin.js в папке routes с помощью приведенного ниже кода.

const express = require('express');
const adminController = require('../controllers/admin');
const router = express.Router();
router.get('/', adminController.getIndex);
router.get('/add-anime', adminController.getAddAnime);
router.get('/edit-anime/:animeId', adminController.getEditAnime);
router.post('/add-anime', adminController.postAnime);
router.post('/edit-anime', adminController.postEditAnime);
router.get('/:animeId', adminController.getAnime);
router.post('/delete', adminController.postDelete);
module.exports = router;

Обновите файл anime.ejs в папке src/pages с помощью приведенного ниже кода.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Anime</title>
    </head>
    <body>
        <h1>Anime</h1>
        <main>
            <h1><%= anime.name %></h1>
            <img src="<%= anime.image %>" alt="<%= anime.name %>" />
            <p><%= anime.description %></p>
            <div>
                <form method="POST" action="/delete">
                    <div>
                        <input type="hidden" value="<%= anime.id %>" name="animeId" />
                        <button>Delete</button>
                    </div>
                </form>
            </div>
            <div>
                <a href="/edit-anime/<%= anime.id %>?edit=true">Edit</a>
            </div>
        </main>
    </body>
</html>

Обновите файл edit-anime.ejs в папке src/pages с помощью приведенного ниже кода.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>
            <% if(editing){ %>Edit Anime<% } else { %>Add Anime<% } %>
        </title>
    </head>
    <body>
        <h1><% if(editing){ %>Edit Anime<% } else { %>Add Anime<% } %></h1>
        <form method="POST" action="/<% if(editing){ %>edit-anime<% } else { %>add-anime<% } %>">
            <div>
                <label>Name</label>
                <input type="text" name="name" value="<% if(editing){ %><%= anime.name %><% } %>" required />
            </div>
            <div>
                <label>Image</label>
                <input type="text" name="image" value="<% if(editing){ %><%= anime.image %><% } %>" required />
            </div>
            <div>
                <label>Description</label>
                <input type="text" name="description" value="<% if(editing){ %><%= anime.description %><% } %>" required />
            </div>
            <% if(editing){ %>
            <div>
                <input type="hidden" name="animeId" value="<%= anime.id %>" />
            </div>
            <% } %>
            <div>
                <button type="submit"><% if(editing){ %>Edit Anime<% } else { %>Add Anime<% } %></button>
            </div>
        </form>
    </body>
</html>

Теперь, когда вы переходите на страницу элемента, вы увидите кнопку редактирования. Когда вы нажмете кнопку, вы попадете в форму, которая теперь была обновлена ​​данными об этих элементах из базы данных. Когда вы обновите элемент, он перенаправит вас на домашнюю страницу, где вы увидите новые изменения.

Реагировать на внешний интерфейс

Поздравляем, вы только что создали полнофункциональное приложение, которое подключается к базе данных mongoDB и имеет полные CRUD-запросы! Однако это еще не приложение MERN, потому что у него нет внешнего интерфейса React. Следующий этап прост, все, что вам нужно сделать, это вернуть серверные данные как json и использовать запросы fetch или axios для получения данных. А что касается формы, вам просто нужно убедиться, что вы отправляете запросы POST на свой внутренний сервер. Мы установили CORS с самого начала, чтобы не возникало ошибок перекрестного происхождения, когда вы пытаетесь подключить внешний интерфейс к серверному. И мы также настраиваем сценарий запуска для совместного запуска серверов внутреннего и внешнего интерфейса, что сделает его лучше.

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

mkdir frontend
cd frontend
npx create-react-app .

Вернитесь в корневую папку для бэкэнда, а затем запустите команду npm run servers, чтобы запустить как бэкенд, так и внешний сервер вместе. Вы должны увидеть, что ваше приложение React работает в браузере.

Теперь перейдите в бэкэнд-папку, перейдите в controllers/admin.js и обновите код на тот, что указан ниже.

Все, что мы делаем, это возвращаем данные, отправленные на индексный маршрут, как .json, чтобы мы могли использовать fetch/axios для сопоставления их во внешнем интерфейсе. Мы также собираемся обновить маршрут POST для добавления нового аниме, чтобы он перенаправлял на индексную страницу внешнего интерфейса React.

const Anime = require('../models/Anime');
exports.getIndex = async (req, res) => {
    const anime = await Anime.find((data) => data);
    try {
        console.log(anime);
        // Data rendered as an object and passed down into index.ejs
        // res.status(200).render('index', { anime: anime });
        // Data returned as json so a fetch/axios requst can get it
        res.json(anime);
    } catch (error) {
        console.log(error);
    }
};
exports.getAnime = async (req, res) => {
    const animeId = req.params.animeId;
    const anime = await Anime.findById(animeId, (anime) => anime);
    try {
        console.log(anime);
        res.status(200).render('anime', { anime: anime });
    } catch (error) {
        console.log(error);
    }
};
exports.getAddAnime = (req, res) => {
    res.status(200).render('edit-anime', { editing: false });
};
exports.getEditAnime = async (req, res) => {
    const animeId = req.params.animeId;
    const editMode = req.query.edit;
    if (!editMode) {
        return res.redirect('/');
    }
    const anime = await Anime.findById(animeId);
    try {
        if (!animeId) {
            return res.redirect('/');
        }
        console.log(anime);
        res.status(200).render('edit-anime', { anime: anime, editing: editMode });
    } catch (error) {
        console.log(error);
    }
};
exports.postAnime = (req, res) => {
    const { name, image, description } = req.body;
    const anime = new Anime({ name: name, image: image, description: description });
    anime.save();
    console.log('Anime Added to the database');
    // Updated the home route to the React App index page
    res.status(201).redirect('http://localhost:3000/');
};
exports.postEditAnime = (req, res) => {
    const animeId = req.body.animeId;
    const { name, image, description } = req.body;
    Anime.findById(animeId)
        .then((anime) => {
            anime.name = name;
            anime.image = image;
            anime.description = description;
            return anime.save();
        })
        .then(() => {
            console.log('Item Updated');
            res.status(201).redirect('/');
        })
        .catch((err) => {
            console.log(err);
        });
};
exports.postDelete = async (req, res) => {
    const animeId = req.body.animeId;
    const anime = await Anime.findByIdAndRemove(animeId, (data) => data);
    try {
        console.log(anime);
        console.log('Item Deleted');
        res.redirect('/');
    } catch (error) {
        console.log(error);
    }
};

Теперь перейдите в папку внешнего интерфейса и перейдите в src/app.js и замените код на тот, что ниже.

import React, { Fragment, useEffect, useState } from 'react';
const App = () => {
    useEffect(() => {
        const getAPI = async () => {
            const response = await fetch('http://localhost:8080/');
            const data = await response.json();
            try {
                console.log(data);
                setLoading(false);
                setAnime(data);
            } catch (error) {
                console.log(error);
            }
        };
        getAPI();
    }, []);
    const [anime, setAnime] = useState([]);
    const [loading, setLoading] = useState(true);
    return (
        <Fragment>
            <h1>Anime Home</h1>
            <div>
                {loading ? (
                    <div>Loading</div>
                ) : (
                    <div>
                        {anime.map((data) => (
                            <div key={data._id}>
                                <ul>
                                    <li>
                                        <h1>
                                            <a href="/{data.id}">{data._id}</a>
                                        </h1>
                                    </li>
                                    <li>
                                        <img src={data.image} alt={data.name} />
                                    </li>
                                    <li>
                                        <p>{data.description}</p>
                                    </li>
                                </ul>
                            </div>
                        ))}
                    </div>
                )}
            </div>
            <div>
                <h1>Add New Anime</h1>
                <form method="POST" action="http://localhost:8080/add-anime">
                    <div>
                        <label>Name</label>
                        <input type="text" name="name" required />
                    </div>
                    <div>
                        <label>Image</label>
                        <input type="text" name="image" required />
                    </div>
                    <div>
                        <label>Description</label>
                        <input type="text" name="description" required />
                    </div>
                    <div>
                        <button type="submit">Add Anime</button>
                    </div>
                </form>
            </div>
        </Fragment>
    );
};
export default App;

Теперь вы должны увидеть ваши данные, отображаемые во внешнем интерфейсе, когда вы перейдете к http://localhost:3000/.

Я также создал форму внизу, которая позволит вам добавлять новые записи в базу данных. Очевидно, что в полном проекте вы должны использовать компоненты для создания своего приложения. Я только что создал быстрый пример, чтобы показать вам, как это выглядит.

Отлично, вы только что создали приложение MERN, это основы! Чтобы завершить приложение, вы должны добавить маршрутизацию на интерфейс с помощью React Router, чтобы вы могли создавать более динамичные страницы. Я предпочитаю использовать Styled Components, но вы можете использовать любую библиотеку CSS, которую захотите. Вы даже можете добавить Redux или другую государственную библиотеку. Просто убедитесь, что вы возвращаете данные из маршрутов GET в бэкэнд, используя .json, чтобы вы могли использовать fetch/axios во внешнем интерфейсе для управления данными.

В качестве альтернативы вы можете просто работать с интерфейсом .ejs и придать стиль и навигацию, используя CSS, на ваше усмотрение. Когда ваше приложение будет готово, просто разверните его на одной из множества доступных платформ, таких как Netlify и Vercel.

Вы можете увидеть мою окончательную версию на моем GitHub в Anime Tracker, не стесняйтесь клонировать и загружать репо. Эта сборка имеет интерфейс .ejs и CSS. Я также внес несколько незначительных изменений в кодовую базу.