创建 MERN Stack 应用程序(2020)

2025-05-24

创建 MERN Stack 应用程序(2020)

开始之前,请确保已安装NodeCreate React App。如果您计划使用本地 mongodb 数据库,也请确保已完成相关设置。

参考资料

Gitflow 工作流
连接到 MongoDB
MongoDB CRUD 操作
MongoDB Atlas
Mongoose
Express.js
EJS
React
React Router
Redux
Netlify
Vercel

所需工具

你可以使用任何代码编辑器和终端应用程序。但对于与后端的 HTTP API 交互,我更喜欢 Postman 应用程序。

代码编辑器:Visual Studio Code
终端:Hyper
API 测试应用程序:Postman

清单

以下是我们将要遵循的步骤

  1. 使用 GIT 工作流初始化项目(可选为项目设置看板)
  2. 设置 MongoDB 数据库(本地或在线)
  3. 创建使用 CRUD 请求连接到数据库的后端 Node/Express 服务器
  4. 使用 EJS 或 React/Redux 创建前端
  • Ejs 模板(HTML 和 CSS Grid/Flexbox)
  • React/Redux(使用 CSS Grid/Flexbox 样式化的组件)
  1. 在线部署到生产服务器(Netlify、Vercel、Heroku 等……)

项目设置

我将创建一个应用程序来追踪我看过的动漫。不过,你可以随意使用你喜欢的主题。

GIT 工作流程

前往 GitHub 创建一个新的 repo,然后在本地机器上创建一个文件夹,并cd使用终端应用程序进入该文件夹。然后像下面这样初始化 repo。

在整个项目过程中,您应该将您的工作提交到 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
Enter fullscreen mode Exit fullscreen mode

设置 MongoDB 数据库

生产环境需要使用在线数据库,本地数据库仅用于开发目的。无论哪种方式,您都可以在本指南中使用您想要的数据库。

在线
https://www.mongodb.com/cloud/atlas
https://mlab.com/

您应该有一个如下所示的连接字符串,用您的凭据替换用户名和密码

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

当地的

确保本地安装了 mongoDB 和 mongoDB compass

在终端中使用以下命令创建您选择的本地数据库

mongo
show dbs;
use animes;
db.createCollection("series");
Enter fullscreen mode Exit fullscreen mode

要连接到数据库,您将使用下面的连接字符串

mongodb://127.0.0.1:27017/animes
Enter fullscreen mode Exit fullscreen mode

设置文件夹结构并安装依赖项

在代码编辑器中打开项目文件夹,创建后端文件夹,然后安装依赖项

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

设置后端文件夹内的文件夹结构

mkdir controllers
mkdir models
mkdir public
mkdir routes
mkdir src
mkdir src/pages
touch app.js
touch .gitignore
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

打开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);
    });
Enter fullscreen mode Exit fullscreen mode

打开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"
    }
}
Enter fullscreen mode Exit fullscreen mode

在终端窗口中使用该命令npm run dev,应用程序应该启动并运行并连接到您的 mongodb 数据库。

树状结构(不显示隐藏文件)


...









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>
Enter fullscreen mode Exit fullscreen mode

然后创建一个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>
Enter fullscreen mode Exit fullscreen mode

最后创建一个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>
Enter fullscreen mode Exit fullscreen mode

接下来创建一个admin.js文件并将其放入controllers文件夹中

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

然后创建一个admin.js文件并将其放入routes文件夹中

const express = require('express');
const adminController = require('../controllers/admin');

const router = express.Router();

router.get('/', adminController.getIndex);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

将管理路由文件导入到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);
Enter fullscreen mode Exit fullscreen mode

创建 Mongoose Schema

在模型文件夹中创建一个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);
Enter fullscreen mode Exit fullscreen mode

创建 CRUD 请求

接下来,我们将创建用于与数据库交互的 CRUD 请求。这也是使用 Postman 应用为所有路由执行 HTTP 请求的绝佳机会。这将允许您在不使用浏览器的情况下 POST 数据并查看 GET 路由。这超出了本指南的范围,但如果您查看文档,就会发现它非常容易使用。

向数据库添加数据(创建)

我们正在为带有添加表单的页面创建路由,并使用 post 路由将该表单数据添加到数据库

使用下面的代码更新文件夹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('/');
};
Enter fullscreen mode Exit fullscreen mode

使用下面的代码更新文件夹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;
Enter fullscreen mode Exit fullscreen mode

现在,如果您访问http://localhost:8080/add-anime并提交一些表单数据,这些数据应该会被添加到您的数据库中。如果您使用的是本地 MongoDB 数据库,请使用 MongoDB Compass 应用检查数据库,您需要刷新数据库才能看到新条目。如果您使用的是在线数据库,只需访问集群即可查看集合。

或者使用 Postman App 向路由http://localhost:8080/add-anime发送 post 请求,如下例所示

邮差

从数据库读取数据(读取)

现在,我们从数据库中检索数据,并通过异步函数调用将其渲染到页面中。我们将使用.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('/');
};
Enter fullscreen mode Exit fullscreen mode

使用下面的代码更新文件夹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;
Enter fullscreen mode Exit fullscreen mode

使用下面的代码更新文件夹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>
Enter fullscreen mode Exit fullscreen mode

使用下面的代码更新文件夹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>
Enter fullscreen mode Exit fullscreen mode

现在,你应该可以看到数据库数据已渲染到主页上,点击其中一个链接后,它会根据链接的 ID 跳转到对应的页面。这些数据也会被记录到控制台中。

从数据库中删除数据(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);
    }
};
Enter fullscreen mode Exit fullscreen mode

使用下面的代码更新文件夹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;
Enter fullscreen mode Exit fullscreen mode

使用下面的代码更新文件夹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>
Enter fullscreen mode Exit fullscreen mode

现在,如果您进入某个项目页面,然后单击“删除”按钮,您应该能够删除它

从数据库更新数据(更新)

现在,我们正在创建用于更新数据库中的每个项目的路由。使用以下代码更新文件。

使用下面的代码更新文件夹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);
    }
};
Enter fullscreen mode Exit fullscreen mode

使用下面的代码更新文件夹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;
Enter fullscreen mode Exit fullscreen mode

使用下面的代码更新文件夹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>
Enter fullscreen mode Exit fullscreen mode

使用下面的代码更新文件夹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>
Enter fullscreen mode Exit fullscreen mode

现在,当您进入某个商品页面时,您会看到一个编辑按钮。点击该按钮后,您将跳转到已使用数据库中该商品数据更新的表单。更新商品后,系统会将您重定向到主页,您将在那里看到新的更改。

React 前端

恭喜,您刚刚创建了一个连接到 MongoDB 数据库并具有完整 CRUD 请求的全栈应用程序!然而,它还不是 MERN 应用,因为它没有 React 前端。下一阶段很简单,您只需返回后端数据,并json使用 fetch 或 axios 请求获取数据即可。至于表单,您只需确保将 POST 请求发送到后端服务器即可。我们从一开始就安装了 CORS,因此当您尝试将前端连接到后端时不会出现跨域错误。我们还设置了一个运行脚本来同时运行后端和前端服务器,这将使其更加完善。

在根文件夹中创建一个前端文件夹,然后在其中设置一个反应应用程序

mkdir frontend
cd frontend
npx create-react-app .
Enter fullscreen mode Exit fullscreen mode

返回后端的根文件夹,然后运行命令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);
    }
};
Enter fullscreen mode Exit fullscreen mode

现在转到前端文件夹并进入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;
Enter fullscreen mode Exit fullscreen mode

现在,当您访问http://localhost:3000/ 时,您应该会看到您的数据在前端呈现

我还在底部创建了一个表单,用于向数据库添加新条目。显然,在一个完整的项目中,你应该使用组件来构建你的应用。我只是创建了一个简单示例来展示它的样子。

干得好,你刚刚创建了一个 MERN 应用,这些就是基础!为了完成这个应用,你应该使用React Router在前端添加路由,以便创建更多动态页面。我推荐使用Styled Components,但你也可以使用任何你想要的 CSS 库。你甚至可以添加 Redux 或其他状态库。只需确保在后端使用 GET 路由返回数据,.json这样你就可以在前端使用 fetch/axios 来管理数据。

或者,您也可以只使用前端,并使用 CSS 来添加样式和导航,一切由您决定。应用完成后,只需将其部署到NetlifyVercel.ejs等众多可用平台之一即可。

你可以在 GitHub 上的Anime Tracker上看到我的最终版本,欢迎随意克隆和下载代码库。此版本包含.ejs前端和 CSS。我还对代码库做了一些细微的调整。

动漫追踪器主页

动漫追踪器添加动漫

动漫追踪器动漫

动漫追踪器 编辑动漫

文章来源:https://dev.to/andrewbaisden/creating-mern-stack-applications-2020-4a44
PREV
如何通过创建 CSS Art 让你成为更优秀的开发人员
NEXT
拥有博客和拥有 GitHub 一样重要的 8 个理由