原文: Let’s build a full stack MongoDB, React, Node and Express (MERN) app
做者:jelo rivera
譯者:博軒
爲保證文章可讀性,本文采用意譯,而非直譯
當我想從前端開發人員進階到全棧開發人員時,我很難找到一篇文章,包含了我所須要學習的所有概念。javascript
例如對數據庫的瞭解,熟悉一門後端語言,如何將先後端整合,這些對於我來講,還有些陌生。這就是促使我完成這篇文章的緣由:解決這個問題,以幫助我本身和其餘前端工程師。css
本文末尾包含了整個項目的 git 倉庫地址,但我仍是建議您先逐步學習本文,再去查看項目源碼。這將幫助您更好地理解整個教程。😀前端
這是咱們的應用程序完成以後的樣子,前端容許咱們作一些增刪改查的操做。java
咱們會從頭開始構建這個應用。設置數據庫,建立後端,並以最小的代價接入前端。node
接下來,讓咱們作好準備,一塊兒完成這個項目!react
讓咱們建立項目的主目錄。這將包含咱們的應用程序的前端和後端的代碼。ios
mkdir fullstack_app && cd fullstack_app
那麼,讓咱們從前端開始吧。咱們將使用 create-react-app
開始構建咱們的前端,這意味着咱們沒必要關注 Webpack
和 Babel
的配置(由於 create-react-app
默認對此進行了配置)。若是您尚未全局安裝 create-react-app
,請使用下面的命令進行安裝。git
sudo npm i -g create-react-app
以後,咱們就可使用 create-react-app
來建立咱們的 React
應用程序。只需在命令行中輸入下面命令便可。github
create-react-app client && cd client // 官方推薦 npx create-react-app client cd client npm start
咱們還須要使用 Axios
來幫助咱們封裝 get/post 請求。如今讓咱們來安裝它:web
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/ ,您如今能夠看到咱們的前端已經啓動了。
回到咱們的主目錄,而後從那裏開始建立咱們的後端目錄。咱們將初始化此目錄,以便爲咱們準備好以後構建須要的 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
數據庫。它是託管在雲端的,這也是咱們行業當前的趨勢,使咱們能使用雲端數據庫。
設置好帳戶後,讓咱們登陸網站。按照網站的提示,逐步建立集羣,以及數據庫管理員。下面是具體的步驟:
一、構建您的第一個集羣
二、建立第一個數據庫用戶
三、將您的 IP
地址列入白名單(一般是你的本機地址)
四、鏈接您的羣集
咱們須要獲取數據庫的鏈接字符串,所以對於第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。