使用 MongoDB,React,Node 和 Express(MERN)構建一個全棧應用

原文:Let’s build a full stack MongoDB, React, Node and Express (MERN) app
作者:jelo rivera
譯者:博軒
爲保證文章可讀性,本文采用意譯,而非直譯

當我想從前端開發人員進階到全棧開發人員時,我很難找到一篇文章,包含了我所需要學習的全部概念。

例如對數據庫的瞭解,熟悉一門後端語言,如何將前後端整合,這些對於我來說,還有些陌生。這就是促使我完成這篇文章的原因:解決這個問題,以幫助我自己和其他前端工程師。

本文末尾包含了整個項目的 git 倉庫地址,但我還是建議您先逐步學習本文,再去查看項目源碼。這將幫助您更好地理解整個教程。😀

“What will we build”

這是我們的應用程序完成之後的樣子,前端允許我們做一些增刪改查的操作。

我們會從頭開始構建這個應用。設置數據庫,創建後端,並以最小的代價接入前端。

接下來,讓我們做好準備,一起完成這個項目!

創建 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 數據庫。它是託管在雲端的,這也是我們行業當前的趨勢,使我們能使用雲端數據庫。

設置好賬戶後,讓我們登錄網站。按照網站的提示,逐步創建集羣,以及數據庫管理員。以下,是創建自己 MongoDB 的步驟:

1、構建您的第一個集羣
圖片描述
2、創建第一個數據庫用戶
圖片描述
3、將您的 IP 地址列入白名單(通常是你的本機地址)
圖片描述
4、連接您的羣集

我們需要獲取數據庫的連接字符串,因此對於第4步,我們只需要單擊創建的集羣的連接按鈕,如下所示。

然後點擊彈窗底部的 “choose a connection method” ,選擇 “Connect your Application” 。然後,複製字符串。

將此字符串 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章