如何使用 MongoDB、Express、React 和 Node.js(MERN 堆栈)创建简单美观的聊天
先决条件
结构
设置客户端
设置服务器
设置我们的 Heroku 托管
设置 GitHub 并链接到 Heroku
完成客户端
最后步骤
最近,我参与了一个有趣的项目,名为SpeedBoard,它是一个用于 Agile 和 Scrum 回顾的实时看板。它是我们在 Scrum Sprint 评审会后使用的工具,方便我们分享关于上一个 Sprint 的反馈。
由于这是一次非常丰富的体验,我想做一个快速教程,介绍如何使用相同的技术栈建立一个简单的聊天室,该技术栈包括:MongoDB、Express、React、Node.js,也称为MERN 技术栈。我还使用了Socket.IO作为实时引擎,以及Material-UI (一个基于Material Design的 React UI 框架)。
如果您不想等到本教程结束,您可以查看最终结果的预览,如果您想分叉并开始改进它,还可以检查 Github 存储库;)
先决条件
在本教程中,我们将使用Heroku托管我们的实时项目,并使用Github托管我们的代码并将其部署到 Heroku,因此请确保您已经拥有他们的帐户,他们都提供免费注册。
结构
在开始之前,我们先快速浏览一下项目的结构。在根文件夹中,我们有两个子文件夹:一个client
包含 React 应用,另一个server
包含 Node.js 服务器:
speedchatapp/
├── client/
├── server/
让我们打开终端并创建项目文件夹:
mkdir speedchatapp
cd speedchatapp/
设置客户端
在客户端,我们将使用Create React App (CRA),它提供了一种非常简单的方法来开始构建任何 React SPA。
CRA 提供了一个非常简单的命令来安装该应用程序,但首先,如果您过去npx
使用过,让我们确保使用的是最新版本:create-react-app
npm uninstall -g create-react-app
现在,让我们client
使用这个简单的命令在我们的文件夹中创建我们的应用程序:
npx create-react-app client
这可能需要几分钟来安装所有依赖项,完成后,尝试:
cd client/
npm start
您现在应该能够通过http://localhost:3000/访问您的应用程序
这真是又快又简单 :) 但距离最终结果还差得远!等项目服务器端准备好后,我们再回来看我们的 React 应用。
设置服务器
现在我们已经准备好了框架client
,让我们看看后端。
首先,让我们server
在项目根目录创建文件夹并初始化package.json
文件:
mkdir server
cd server/
npm init
实用程序将引导您完成文件的配置,但您可以输入Enter本教程的所有选项。
现在,我们将使用以下命令安装服务器所需的所有依赖项(Express,Mongoose 和 Socket.IO):
npm install express mongoose socket.io --save
然后,将.gitignore
文件从client
文件夹复制到server
文件夹,以防止某些文件和文件夹被推送到我们的GitHub存储库(例如/node_modules
文件夹):
cp ../client/.gitignore ./
我们将创建服务器运行所需的两个文件。第一个文件(Message.js)是我们将保存在数据库中的文档的架构。我们需要三个信息:在聊天中发送消息的用户的姓名、消息内容以及用于记录发送消息时间的时间戳。
server/Message.js
const mongoose = require('mongoose');
const messageSchema = new mongoose.Schema({
content: String,
name: String,
}, {
timestamps: true,
});
module.exports = mongoose.model('Message', messageSchema);
第二个(index.js)是我们的主文件,我不会讲太多细节,因为这会使本教程有点太长,但请随时在评论中提出任何问题,我很乐意回答它们或在必要时直接在代码中改进评论。
server/index.js
const express = require('express');
const app = express();
const http = require('http').Server(app);
const path = require('path');
const io = require('socket.io')(http);
const uri = process.env.MONGODB_URI;
const port = process.env.PORT || 5000;
const Message = require('./Message');
const mongoose = require('mongoose');
mongoose.connect(uri, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
app.use(express.static(path.join(__dirname, '..', 'client', 'build')));
io.on('connection', (socket) => {
// Get the last 10 messages from the database.
Message.find().sort({createdAt: -1}).limit(10).exec((err, messages) => {
if (err) return console.error(err);
// Send the last messages to the user.
socket.emit('init', messages);
});
// Listen to connected users for a new message.
socket.on('message', (msg) => {
// Create a message with the content and the name of the user.
const message = new Message({
content: msg.content,
name: msg.name,
});
// Save the message to the database.
message.save((err) => {
if (err) return console.error(err);
});
// Notify all other users about a new message.
socket.broadcast.emit('push', msg);
});
});
http.listen(port, () => {
console.log('listening on *:' + port);
});
您的项目结构现在应如下所示:
speedchatapp/
├── client/
│ └── (Several files and folders)
└── server/
├── node_modules/
├── .gitignore
├── index.js
├── Message.js
├── package-lock.json (auto-generated)
└── package.json
在回到我们的 React 应用程序完成我们的项目之前,让我们设置我们的 Heroku 托管并将其链接到我们的 Github 存储库,以确保部署正常工作。
设置我们的 Heroku 托管
让我们下载并安装Heroku CLI来从我们的终端设置一切。
下载并安装后,让我们返回终端并登录我们的 Heroku 帐户:
heroku login
它将在您的浏览器中打开一个新选项卡,一旦您登录,您就可以关闭浏览器选项卡并返回到您的终端。
现在让我们创建托管我们项目的新应用程序:
heroku create
它将自动生成一个带有 URL 的标识符,您可以通过该 URL 访问您的应用程序,它看起来应该像这样:
https://sleepy-meadow-81798.herokuapp.com/
如果您想要一个更容易记住的名称,您可以重命名您的应用程序,然后您可以在本教程的其余部分使用它:
好了,现在我们需要 MongoDB 数据库来存储用户的聊天消息。让我们将 mongolab 插件添加到我们的应用中:
heroku addons:create mongolab --app speedchatapp
我speedchatapp
在上一个命令中使用了它是因为我重命名了我的应用程序,但如果您没有重命名它,则应该使用创建它时提供的那个,例如sleepy-meadow-81798
。
创建后,它将以绿色显示变量的名称,即MONGODB_URI
。现在让我们获取新创建的数据库的配置 URI:
heroku config:get MONGODB_URI
您应该看到类似这样的内容:
mongodb://heroku_123abc:abc123@ds141188.mlab.com:41188/heroku_123abc
复制此 URI,并在项目根目录下创建一个名为 的文件,.env
其内容如下[VARIABLE_IN_GREEN]=[URI]
。它应该如下所示:
MONGODB_URI=mongodb://heroku_123abc:abc123@ds141188.mlab.com:41188/heroku_123abc
让我们再复制一次并在其末尾.gitignore
添加文件,以避免将数据库的凭据推送到 GitHub:.env
cp server/.gitignore ./
echo '.env' >> .gitignore
在部署应用期间,我们需要告诉 Heroku 如何启动服务器。我们可以创建一个Procfile 文件,并将其放在项目的根目录下。接下来,我们创建它并添加启动服务器的命令行:
echo 'web: node server/index.js' > Procfile
现在让package.json
我们在项目的根目录下初始化另一个。和以前一样,不用担心所有选项,现在只需Enter在所有提示符下输入:
npm init
我们要做的最后一件事是安装名为Concurrently的 npm 包,它将允许我们在开发模式下在单个命令行中运行服务器和客户端:
npm install --save-dev concurrently
最后,在我们新创建的package.json
项目的根目录中,我们将在scripts
部分中添加两行:
"scripts": {
"dev": "concurrently --kill-others \"heroku local\" \"npm run start --prefix ./client\"",
"postinstall": "npm install --prefix ./server && npm install --prefix ./client && npm run build --prefix ./client",
}
正如您所猜测的,该postinstall
命令将在 Heroku 完成运行npm install
我们文件夹根目录下的命令后执行。它告诉 Heroku 也在我们的and文件夹npm install
中运行该命令,并且还将构建我们的 React 应用以供生产环境使用。client
server
现在,是时候测试它了,转到项目的根目录并输入:
npm run dev
这将以开发模式启动服务器和我们的 React 应用程序,并且它将在您的浏览器中打开一个窗口,其中包含我们 React 应用程序的上一个登录页面。
在你的终端中,你应该看到如下内容:
> concurrently --kill-others "heroku local" "npm run start --prefix ./client"
[1]
[1] > react-scripts start
[1]
[0] [OKAY] Loaded ENV .env File as KEY=VALUE Format
[0] 12:16:15 PM web.1 | listening on *:5000
[1] Starting the development server...
[1]
[1] Compiled successfully!
[1]
[1] You can now view client in the browser.
[1]
[1] Local: http://localhost:3000/
[1] On Your Network: http://192.168.0.10:3000/
[1]
[1] Note that the development build is not optimized.
[1] To create a production build, use npm run build.
注意:我们在开发和实时模式下使用相同的数据库,如果您想使用不同的数据库,您可以像我们之前看到的那样在 Heroku 中创建另一个数据库,并.env
使用新数据库的凭据更新您的文件,以确保它不会干扰生产中的数据库。
设置 GitHub 并链接到 Heroku
现在,我们将在 GitHub 上创建一个新的存储库,并将其连接到 Heroku,这样每次我们在主分支上合并 Pull 请求时,它都会自动将其部署到 Heroku 上。
让我们在 GitHub 上创建我们的仓库。前往https://github.com/new:
写下我们下一步要使用的仓库 URL。回到我们的终端,在项目的根文件夹中:
// Initialize the root folder as a Git repository
git init
// Add all the files for the initial commit
git add .
// Commit staged files
git commit -m "Initial commit"
// Set the GitHub remote repository
git remote add origin <repository url>
// Push the local changes to GitHub
git push origin master
现在我们的代码在 GitHub 上,让我们将这个存储库链接到我们的 Heroku 应用程序。
在Heroku UI中,选择您的应用并单击Deploy
选项卡。在 中Deployment method
,单击Github
,输入您的存储库名称,然后单击Connect
:
另外,确保master
分支上的“启用自动部署”已激活:
现在看起来应该是这样的:
现在让我们触发第一次手动部署来检查一切是否正常。点击Deploy Branch
,等待直到看到Your app was successfully deployed
。
最后,单击Open App
页面右上角的按钮后,您应该会在 Heroku 托管上看到 React 应用程序。
从现在开始,将任何更新推送到你的 GitHub 存储库后,你应该会在 Heroku UI 中看到自动触发的部署:
完成客户端
现在我们的项目架构已经准备好了,让我们完成我们的client
React 应用程序。
我们首先需要在client
文件夹中安装前端依赖项:客户端的 Socket.IO、Material-UI 核心和图标:
cd client/
npm install socket.io-client @material-ui/core @material-ui/icons --save
现在,在文件末尾client/package.json
添加以下字段:proxy
"proxy": "http://localhost:5000"
它会告诉开发服务器将任何未知请求代理到你的开发服务器。更多信息请查看官方文档。
接下来,我们将创建一个config.js
文件来告诉我们的应用程序在本地机器或实时托管时切换端点:
客户端/src/config.js
import pkg from '../package.json';
export default {
development: {
endpoint: pkg.proxy
},
production: {
endpoint: window.location.hostname
}
}
好的,现在让我们从根文件夹启动本地开发环境:
npm run dev
最后步骤
对于最后一步,请手动创建或更新下面的每个文件,或者直接转到 GitHub 存储库来检出项目。
代替client/src/App.css
:
body {
background: #f5f5f5;
padding: 16px;
}
#chat {
max-height: calc(100vh - 128px);
overflow: scroll;
padding: 16px;
}
.name {
color: rgba(0, 0, 0, 0.54);
}
.content {
margin-bottom: 8px;
}
代替client/src/App.js
:
import React from 'react';
import config from './config';
import io from 'socket.io-client';
import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
import BottomBar from './BottomBar';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
chat: [],
content: '',
name: '',
};
}
componentDidMount() {
this.socket = io(config[process.env.NODE_ENV].endpoint);
// Load the last 10 messages in the window.
this.socket.on('init', (msg) => {
let msgReversed = msg.reverse();
this.setState((state) => ({
chat: [...state.chat, ...msgReversed],
}), this.scrollToBottom);
});
// Update the chat if a new message is broadcasted.
this.socket.on('push', (msg) => {
this.setState((state) => ({
chat: [...state.chat, msg],
}), this.scrollToBottom);
});
}
// Save the message the user is typing in the input field.
handleContent(event) {
this.setState({
content: event.target.value,
});
}
//
handleName(event) {
this.setState({
name: event.target.value,
});
}
handleSubmit(event) {
// Prevent the form to reload the current page.
event.preventDefault();
// Send the new message to the server.
this.socket.emit('message', {
name: this.state.name,
content: this.state.content,
});
this.setState((state) => {
// Update the chat with the user's message and remove the current message.
return {
chat: [...state.chat, {
name: state.name,
content: state.content,
}],
content: '',
};
}, this.scrollToBottom);
}
// Always make sure the window is scrolled down to the last message.
scrollToBottom() {
const chat = document.getElementById('chat');
chat.scrollTop = chat.scrollHeight;
}
render() {
return (
<div className="App">
<Paper id="chat" elevation={3}>
{this.state.chat.map((el, index) => {
return (
<div key={index}>
<Typography variant="caption" className="name">
{el.name}
</Typography>
<Typography variant="body1" className="content">
{el.content}
</Typography>
</div>
);
})}
</Paper>
<BottomBar
content={this.state.content}
handleContent={this.handleContent.bind(this)}
handleName={this.handleName.bind(this)}
handleSubmit={this.handleSubmit.bind(this)}
name={this.state.name}
/>
</div>
);
}
};
export default App;
创造client/src/BottomBar.js
:
import React from 'react';
import { fade, makeStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import InputBase from '@material-ui/core/InputBase';
import Toolbar from '@material-ui/core/Toolbar';
import ChatIcon from '@material-ui/icons/Chat';
import FaceIcon from '@material-ui/icons/Face';
const useStyles = makeStyles(theme => ({
appBar: {
bottom: 0,
top: 'auto',
},
inputContainer: {
backgroundColor: fade(theme.palette.common.white, 0.15),
'&:hover': {
backgroundColor: fade(theme.palette.common.white, 0.25),
},
borderRadius: theme.shape.borderRadius,
marginLeft: theme.spacing(1),
position: 'relative',
width: '100%',
},
icon: {
width: theme.spacing(7),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
inputRoot: {
color: 'inherit',
},
inputInput: {
padding: theme.spacing(1, 1, 1, 7),
width: '100%',
},
}));
export default function BottomBar(props) {
const classes = useStyles();
return (
<AppBar position="fixed" className={classes.appBar}>
<Toolbar>
<div className={classes.inputContainer} style={{maxWidth: '200px'}}>
<div className={classes.icon}>
<FaceIcon />
</div>
<InputBase
onChange={props.handleName}
value={props.name}
placeholder="Name"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
inputProps={{ 'aria-label': 'name' }}
/>
</div>
<div className={classes.inputContainer}>
<form onSubmit={props.handleSubmit}>
<div className={classes.icon}>
<ChatIcon />
</div>
<InputBase
onChange={props.handleContent}
value={props.content}
placeholder="Type your message..."
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
inputProps={{ 'aria-label': 'content' }}
/>
</form>
</div>
</Toolbar>
</AppBar>
);
}
每次更新代码时,您都应该看到http://localhost:3000上的项目自动重新加载最新的更改。
最后,让我们将最新更新推送到 GitHub,以触发我们实时项目的新部署:
git add .
git commit -m "Final update"
git push origin master
瞧,一切就绪!我们的聊天功能现已完成,随时可用:https://speedchatapp.herokuapp.com/
如果您有任何疑问,请随时在评论区提问,我很乐意解答并改进本教程。也欢迎您fork 该项目来改进它;)
文章来源:https://dev.to/armelpingault/how-to-create-a-simple-and-beautiful-chat-with-mongodb-express-react-and-node-js-mern-stack-29l6