【译】使用 MongoDB,React,Node 和 Express(MERN)构建一个全栈应用-LMLPHP

当我想从前端开发人员进阶到全栈开发人员时,我很难找到一篇文章,包含了我所需要学习的全部概念。

例如对数据库的了解,熟悉一门后端语言,如何将前后端整合,这些对于我来说,还有些陌生。这就是促使我完成这篇文章的原因:解决这个问题,以帮助我自己和其他前端工程师。

本文末尾包含了整个项目的 git 仓库地址,但我还是建议您先逐步学习本文,再去查看项目源码。这将帮助您更好地理解整个教程。😀

【译】使用 MongoDB,React,Node 和 Express(MERN)构建一个全栈应用-LMLPHP

这是我们的应用程序完成之后的样子,前端允许我们做一些增删改查的操作。

我们会从头开始构建这个应用。设置数据库,创建后端,并以最小的代价接入前端。

接下来,让我们做好准备,一起完成这个项目!

创建 Client

让我们创建项目的主目录。这将包含我们的应用程序的前端和后端的代码。

mkdir fullstack_app && cd fullstack_app

那么,让我们从前端开始吧。我们将使用 create-react-app 开始构建我们的前端,这意味着我们不必关注 WebpackBabel 的配置(因为 create-react-app 默认对此进行了配置)。如果您还没有全局安装 create-react-app,请使用下面的命令进行安装。

sudo npm i -g create-react-app

之后,我们就可以使用 create-react-app 来创建我们的 React 应用程序。只需在命令行中输入下面命令即可。

create-react-app client && cd client

// 官方推荐
npx create-react-app client
cd client
npm start

我们还需要使用 Axios 来帮助我们封装 get/post 请求。现在让我们来安装它:

npm i -S axios

等待安装完毕,我们继续组织前端代码,以便我们之后接入后端。

PC 端用户:

del src\App.css src\App.test.js src\index.css src\logo.svg\

MAC 端用户:

rm src/App.css src/App.test.js src/index.css src/logo.svg

然后,让我们在 client 文件夹中编辑我们的 App.js 文件,让它只是渲染一些简单的东西。在我们准备好后端时,我们将进一步编辑此文件。

// client/src/App.js
import React, { Component } from "react";

class App extends Component {
  render() {
    return <div>I'M READY TO USE THE BACK END APIS! :-)</div>;
  }
}

export default App;

我们还需要编辑 index.js 并删除一行代码。我们需要删除 ‘./index.css’;现在,我们可以启动我们的 react 应用了。

// client/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

现在,只需要在命令行中输入:

npm start

接下来,打开浏览器并输入 http://localhost:3000/ ,您现在可以看到我们的前端已经启动了。

创建 Backend

回到我们的主目录,然后从那里开始创建我们的后端目录。我们将初始化此目录,以便为我们准备好之后构建需要的 package.json 。您将在终端看到一些 package.json 配置的详细信息,只需要按回车键直到完成即可。

mkdir backend && cd backend
npm init
// 也可以使用 npm init -y 来加速初始化

创建一个新文件,作为后端的主要代码,并将其命名为 server.js。然后,在其中写入以下内容。这部分后端代码非常简洁、基础,我直接创建它,以便初学者不必考虑代码的复杂性,从而更快的理解代码的意图。然后,一旦理解了这段代码的意图,后续的操作也会更加轻松。为了便于理解,我在每个方法旁边都添加了注释。

const mongoose = require('mongoose');
const express = require('express');
var cors = require('cors');
const bodyParser = require('body-parser');
const logger = require('morgan');
const Data = require('./data');

const API_PORT = 3001;
const app = express();
app.use(cors());
const router = express.Router();

//这是我们的MongoDB数据库
const dbRoute =
  'mongodb://<your-db-username-here>:<your-db-password-here>@ds249583.mlab.com:49583/fullstack_app';

//将我们的后端代码与数据库连接起来
mongoose.connect(dbRoute, { useNewUrlParser: true });

let db = mongoose.connection;

db.once('open', () => console.log('connected to the database'));

//检查与数据库的连接是否成功
db.on('error', console.error.bind(console, 'MongoDB connection error:'));

//(可选)仅用于记录和
// bodyParser,将请求体解析为可读的json格式
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(logger('dev'));

//这是我们的get方法
//此方法获取数据库中的所有可用数据
router.get('/getData', (req, res) => {
  Data.find((err, data) => {
    if (err) return res.json({ success: false, error: err });
    return res.json({ success: true, data: data });
  });
});

//这是我们的更新方法
//此方法会覆盖数据库中的现有数据
router.post('/updateData', (req, res) => {
  const { id, update } = req.body;
  Data.findByIdAndUpdate(id, update, (err) => {
    if (err) return res.json({ success: false, error: err });
    return res.json({ success: true });
  });
});

//这是我们的删除方法
//此方法删除数据库中的现有数据
router.delete('/deleteData', (req, res) => {
  const { id } = req.body;
  Data.findByIdAndRemove(id, (err) => {
    if (err) return res.send(err);
    return res.json({ success: true });
  });
});

//这是我们的创造方法
//此方法在我们的数据库中添加新数据
router.post('/putData', (req, res) => {
  let data = new Data();

  const { id, message } = req.body;

  if ((!id && id !== 0) || !message) {
    return res.json({
      success: false,
      error: 'INVALID INPUTS',
    });
  }
  data.message = message;
  data.id = id;
  data.save((err) => {
    if (err) return res.json({ success: false, error: err });
    return res.json({ success: true });
  });
});

//为我们的http请求添加 /api
app.use('/api', router);

//将我们的后端发送到端口
app.listen(API_PORT, () => console.log(`LISTENING ON PORT ${API_PORT}`));

您可能已经注意到我们的后端代码中已经设置了数据库的链接。别担心,这是我们文章的下一步。设置它会像之前那几步同样简单。首先,访问:MongoDB atlas,并创建一个账户。MongoDB atlas 将为我们提供一个免费的 500MBMongoDB 数据库。它是托管在云端的,这也是我们行业当前的趋势,使我们能使用云端数据库。

设置好账户后,让我们登录网站。按照网站的提示,逐步创建集群,以及数据库管理员。下面是具体的步骤:

1、构建您的第一个集群
【译】使用 MongoDB,React,Node 和 Express(MERN)构建一个全栈应用-LMLPHP
2、创建第一个数据库用户
【译】使用 MongoDB,React,Node 和 Express(MERN)构建一个全栈应用-LMLPHP
3、将您的 IP 地址列入白名单(通常是你的本机地址)
【译】使用 MongoDB,React,Node 和 Express(MERN)构建一个全栈应用-LMLPHP
4、连接您的群集

我们需要获取数据库的连接字符串,因此对于第4步,我们只需要单击创建的集群的连接按钮,如下所示。

【译】使用 MongoDB,React,Node 和 Express(MERN)构建一个全栈应用-LMLPHP

然后点击弹窗底部的 “choose a connection method” ,选择 “Connect your Application” 。然后,复制字符串。

【译】使用 MongoDB,React,Node 和 Express(MERN)构建一个全栈应用-LMLPHP

将此字符串 uri 粘贴到 server.js 文件中。找到 dbRoute 变量,将连接替换,并将你之前设置好的用户名,密码替换。

现在,回到我们的后端源代码。我们现在将配置我们的数据库,创建一个名为 data.js 的文件。代码如下:

// /backend/data.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

// this will be our data base's data structure
const DataSchema = new Schema(
  {
    id: Number,
    message: String
  },
  { timestamps: true }
);

// export the new Schema so we could modify it using Node.js
module.exports = mongoose.model("Data", DataSchema);

到了这里,我们几乎已经完成了!让我们使用如下命令为后端安装依赖:

npm i -S mongoose express body-parser morgan cors

启动后端:

node server.js

我们可以在我们的控制台中看到它已准备好并正在侦听端口 3001 。让我们回到前端完成 MongoDB + Node.JS + Express.JS 系统所需的UI。

回到 /client/src/App.js 做出如下修改:

// /client/App.js
import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
  // 初始化组件的状态
  state = {
    data: [],
    id: 0,
    message: null,
    intervalIsSet: false,
    idToDelete: null,
    idToUpdate: null,
    objectToUpdate: null,
  };

  // 当组件加载时,它首先要从数据库中获取所有的数据,这里会设置一个轮询逻辑,及时将数据在 `UI` 中更新。
  componentDidMount() {
    this.getDataFromDb();
    if (!this.state.intervalIsSet) {
      let interval = setInterval(this.getDataFromDb, 1000);
      this.setState({ intervalIsSet: interval });
    }
  }

  // 永远不要让一个进程持续存在
  // 当我们结束使用时,一定要杀死这个进程
  componentWillUnmount() {
    if (this.state.intervalIsSet) {
      clearInterval(this.state.intervalIsSet);
      this.setState({ intervalIsSet: null });
    }
  }


  // 我们的第一个使用后端api的get方法
  // 从我们的数据库中获取数据
  getDataFromDb = () => {
    fetch('http://localhost:3001/api/getData')
      .then((data) => data.json())
      .then((res) => this.setState({ data: res.data }));
  };

  // 使用 put 方法,在数据库里面插入一条新的数据
  putDataToDB = (message) => {
    let currentIds = this.state.data.map((data) => data.id);
    let idToBeAdded = 0;
    while (currentIds.includes(idToBeAdded)) {
      ++idToBeAdded;
    }

    axios.post('http://localhost:3001/api/putData', {
      id: idToBeAdded,
      message: message,
    });
  };

  // 我们的删除方法使用我们的后端api
  // 删除现有数据库信息
  deleteFromDB = (idTodelete) => {
    parseInt(idTodelete);
    let objIdToDelete = null;
    this.state.data.forEach((dat) => {
      if (dat.id == idTodelete) {
        objIdToDelete = dat._id;
      }
    });

    axios.delete('http://localhost:3001/api/deleteData', {
      data: {
        id: objIdToDelete,
      },
    });
  };

  // 我们的更新方法使用我们的后端api
  // 覆盖现有的数据库信息
  updateDB = (idToUpdate, updateToApply) => {
    let objIdToUpdate = null;
    parseInt(idToUpdate);
    this.state.data.forEach((dat) => {
      if (dat.id == idToUpdate) {
        objIdToUpdate = dat._id;
      }
    });

    axios.post('http://localhost:3001/api/updateData', {
      id: objIdToUpdate,
      update: { message: updateToApply },
    });
  };

  render() {
    const { data } = this.state;
    return (
      <div>
        <ul>
          {data.length <= 0
            ? 'NO DB ENTRIES YET'
            : data.map((dat) => (
                <li style={{ padding: '10px' }} key={data.message}>
                  <span style={{ color: 'gray' }}> id: </span> {dat.id} <br />
                  <span style={{ color: 'gray' }}> data: </span>
                  {dat.message}
                </li>
              ))}
        </ul>
        <div style={{ padding: '10px' }}>
          <input
            type="text"
            onChange={(e) => this.setState({ message: e.target.value })}
            placeholder="add something in the database"
            style={{ width: '200px' }}
          />
          <button onClick={() => this.putDataToDB(this.state.message)}>
            ADD
          </button>
        </div>
        <div style={{ padding: '10px' }}>
          <input
            type="text"
            style={{ width: '200px' }}
            onChange={(e) => this.setState({ idToDelete: e.target.value })}
            placeholder="put id of item to delete here"
          />
          <button onClick={() => this.deleteFromDB(this.state.idToDelete)}>
            DELETE
          </button>
        </div>
        <div style={{ padding: '10px' }}>
          <input
            type="text"
            style={{ width: '200px' }}
            onChange={(e) => this.setState({ idToUpdate: e.target.value })}
            placeholder="id of item to update here"
          />
          <input
            type="text"
            style={{ width: '200px' }}
            onChange={(e) => this.setState({ updateToApply: e.target.value })}
            placeholder="put new value of the item here"
          />
          <button
            onClick={() =>
              this.updateDB(this.state.idToUpdate, this.state.updateToApply)
            }
          >
            UPDATE
          </button>
        </div>
      </div>
    );
  }
}

export default App;

最后,我们编辑 package.json,并在那里添加一个代理指向后端部署的端口。

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.18.0",
    "react": "^16.5.0",
    "react-dom": "^16.5.0",
    "react-scripts": "1.1.5"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "proxy": "http://localhost:3001"
}

请记住,这是前端部分的 package.json ,我们需要在 client 目录中去编辑。

现在,还剩下最后一点,就是同时启动后端和前端项目。

为此,让我们回到项目的根目录输入下面命令:

npm init -y
npm i -S concurrently

编辑主项目目录的 package.json

{
  "name": "fullstack_app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "concurrently \"cd backend && node server.js\" \"cd client && npm start\""
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "concurrently": "^4.0.1"
  }
}

现在,在命令行输入 npm start 启动我们的应用,它会帮我们打开浏览器,看到这个 MERN (全栈) 的应用。我们可以在此之上任意扩展我们想要的功能。

哦,还有一件事。确保在浏览器上启用 CORS,这使我们通过自己的机器调用自己的 API。这是一个很棒的插件。😬

最后,这里是 git repo

08-15 23:35