原文:Let’s build a full stack MongoDB, React, Node and Express (MERN) app
作者:jelo rivera
譯者:博軒
為保證文章可讀性,本文采用意譯,而非直譯
當我想從前端開發人員進階到全棧開發人員時,我很難找到一篇文章,包含了我所需要學習的全部概念。
例如對資料庫的瞭解,熟悉一門後端語言,如何將前後端整合,這些對於我來說,還有些陌生。這就是促使我完成這篇文章的原因:解決這個問題,以幫助我自己和其他前端工程師。
本文末尾包含了整個專案的 git 倉庫地址,但我還是建議您先逐步學習本文,再去檢視專案原始碼。這將幫助您更好地理解整個教程。?
這是我們的應用程式完成之後的樣子,前端允許我們做一些增刪改查的操作。
我們會從頭開始構建這個應用。設定資料庫,建立後端,並以最小的代價接入前端。
接下來,讓我們做好準備,一起完成這個專案!
建立 Client
讓我們建立專案的主目錄。這將包含我們的應用程式的前端和後端的程式碼。
mkdir fullstack_app && cd fullstack_app
那麼,讓我們從前端開始吧。我們將使用 create-react-app
開始構建我們的前端,這意味著我們不必關注 Webpack
和 Babel
的配置(因為 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 將為我們提供一個免費的 500MB
的 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。