原文: 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。