共计 8227 个字符,预计需要花费 21 分钟才能阅读完成。
原文: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
数据库。它是托管在云端的,这也是我们行业当前的趋势,使我们能使用云端数据库。
设置好账户后,让我们登录网站。按照网站的提示,逐步创建集群,以及数据库管理员。以下,是创建自己 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。