Skip to content

Latest commit

 

History

History
1439 lines (1038 loc) · 40.3 KB

File metadata and controls

1439 lines (1038 loc) · 40.3 KB

五、使用 MERN 构建一个基于照片的社交网络

在这一章中,你将使用 MERN 框架建立一个基于照片的社交网络。后端托管在 Heroku,前端站点使用 Firebase 托管。Firebase 也处理身份验证功能。

Material-UI 提供了项目中的图标。使用 Pusher 是因为 MongoDB 不像 Firebase 那样是实时数据库。您希望帖子反映出某人点击提交按钮的瞬间。

有了这个基于照片的功能性社交网络,你可以从你的电脑上传图片并写下描述。用户通过电子邮件登录。最终托管的 app 如图 5-1 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig1_HTML.jpg

图 5-1

最终应用

首先,在你的终端上创建一个photo-social-mern文件夹。在里面,它使用创建-反应-应用来创建一个名为照片-社交-前端的新应用。以下是命令。

mkdir photo-social-mern
cd photo-social-mern
npx create-react-app photo-social-frontend

Firebase 托管初始设置

由于前端站点是通过 Firebase 托管的,所以可以在 create-react-app 创建 React app 的同时创建基本设置。按照第 1 章的设置说明,我在 Firebase 控制台中创建了图片社交网

由于使用了认证功能,您需要进行第 4 章中提到的额外配置,并使用您需要复制的firebaseConfig(参见图 5-2 )。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig2_HTML.jpg

图 5-2

配置

在 Visual Studio Code (VSCode)中打开代码,在src文件夹中创建一个firebase.js文件,并将配置内容粘贴到那里。

const firebaseConfig = {
    apiKey: "AIxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxY",
    authDomain: "photo-xxxxxxxxxxxxxxxxxxxxxxx.com",
    projectId: "photo-xxxxxxxxxxx",
    storageBucket: "photo-xxxxxxxxxxxx",
    messagingSenderId: "52xxxxxxx",
    appId: "1:52xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};

React 基本设置

返回 React 项目,将cd返回到photo-social-frontend目录。用npm start启动 React 应用。

cd photo-social-frontend
npm start

index.jsApp.jsApp.css中删除文件和基本设置就像在第 2 章中所做的一样。遵循这些指示。图 5-3 显示了应用在 localhost 上的外观。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig3_HTML.jpg

图 5-3

初始应用

创建标题组件

让我们创建应用标题,这是一个很好的标志。在App.js文件中,用app__header类名创建一个 div,并使用 public 文件夹中的 React 徽标,这是每个 React 项目都附带的。更新的内容用粗体标记。

import './App.css';
function App() {
  return (
    <div className="app">
      <div className="app__header">
        <img className="app__headerImage" src="logo192.png" alt="Header" />
      </div>
    </div>
  );
}

export default App;

接下来,开始在App.css文件中编写样式。在这里,您为应用、app__headerapp__headerImage类编写样式。

.app {
  background-color: #fafafa;
}

.app__header{
  background-color: white;
  padding: 20px;
  border-bottom: 1px solid lightgray;
  object-fit: contain;
}

.app__headerImage {
  object-fit: contain;
  margin-left: 10px;
  height: 40px;
}

5-4 显示了 localhost 上的 logo。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig4_HTML.jpg

图 5-4

完美的标志

创建帖子组件

现在让我们创建 post 组件,它包含登录用户的头像,包括一张照片和一个简短的描述。在src文件夹中创建一个components文件夹。然后,在components文件夹中创建两个文件——Post.jsPost.css

Post.js文件是一个简单的功能组件,包含用户名、图片和帖子。

import React from 'react'
import './Post.css'
const Post = () => {
    return (
        <div className="post">
            <h3>TWD</h3>
            <img className="post__image" src="https://www.techlifediary.com/wp-content/uploads/2020/06/react-js.png" alt="React" />
            <h4 className="post__text"><strong>thewebdev</strong>&#x1F525;Build a Messaging app with MERN (MongoDB, Express, React JS, Node JS) &#x1F525;</h4>
        </div>
    )
}

export default Post

App.js文件中,包含三次Post组件。更新的内容用粗体标记。

import './App.css';
import Post from './components/Post';
function App() {
  return (
    <div className="app">
      <div className="app__header">
        <img className="app__headerImage" src="logo192.png" alt="Header" />
      </div>
       <Post />
        <Post />
        <Post />
    </div>
  );
}

export default App;

图标来自于 Material-UI ( https://material-ui.com )。首先,根据文档进行两次 npm 安装。通过photo-social-frontend文件夹中的集成端子安装铁芯。

npm i @material-ui/core @material-ui/icons

Post.js中,从 Material-UI 添加一个头像图标。在一个post__header div 中,它和h3标签一起使用。更新的内容用粗体标记。

...
import { Avatar } from '@material-ui/core'

const Post = () => {
    return (
        <div className="post">
           <div className="post__header">
                <Avatar
                    className="post__avatar"
                    alt="TWD"
                    src="/statimg/avatar/1.jpg"
                />
                <h3>TWD</h3>
            </div>
            <img className="post__image" src="https://www.techlifediary.com/wp-content/uploads/2020/06/react-js.png" alt="React" />
            ...
        </div>
    )
}

export default Post

接下来,在Post.css文件中添加样式。

.post {
    background-color: white;
    max-width: 800px;
    border: 1px solid lightgray;
    margin-bottom: 45px;
}
.post__image {
    width: 100%;
    object-fit: contain;
    border-top: 1px solid lightgray;
    border-bottom: 1px solid lightgray;
}
.post__text {
    font-weight: normal;
    padding: 20px;
}

.post__header {
    display: flex;
    align-items: center;
    padding: 20px;
}
.post__avatar {
    margin-right: 10px;
}

5-5 显示了应用现在在 localhost 上的样子。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig5_HTML.jpg

图 5-5

风格帖子

使组件动态化

让我们把一切都动态化,把用户名、标题和图片 URL 作为道具传递。在Post.js中,进行以下更改。更新的内容用粗体标记。

...
import { Avatar } from '@material-ui/core'

const Post = ({ username, caption, imageUrl }) => {
    return (
        <div className="post">
           <div className="post__header">
                <Avatar
                    className="post__avatar"
                    alt= {username}
                    src="/statimg/avatar/1.jpg"
                />
                <h3> {username}</h3>
            </div>
            <img className="post__image" src={imageUrl} alt="React" />
            <h4 className="post__text"><strong>{username}</strong>{caption}</h4>
        </div>
    )
}

export default Post

接下来,我们来优化一下App.js中的代码。这里,您使用useState钩子来创建新的状态帖子。这里的柱子是数组中的对象。

在 return 语句中,映射 posts 数组并显示每个帖子。更新的内容用粗体标记。

...
import React, { useEffect, useState } from 'react';
function App() {
  const [posts, setPosts] = useState([
    {
      username: "TWD",
      caption: "&#x1F525;Build a Messaging app with MERN Stack&#x1F525;",
      imageUrl: "https://www.techlifediary.com/wp-content/uploads/2020/06/react-js.png"
    },
    {
      username: "nabendu82",
      caption: "Such a beautiful world",
      imageUrl: "https://quotefancy.com/media/wallpaper/3840x2160/126631-Charles-Dickens-Quote-And-a-beautiful-world-you-live-in-when-it-is.jpg"
     }
  ])
  return (
    <div className="app">
      <div className="app__header">
        <img className="app__headerImage" src="logo192.png" alt="Header" />
      </div>
        {posts.map(post => (
            <Post username={post.username} caption={post.caption} imageUrl={post.imageUrl} />
        ))}
    </div>
  );
}

export default App;

5-6 显示在 localhost 上。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig6_HTML.jpg

图 5-6

一切动态

Firebase 身份验证设置

让我们来看看 Firebase 身份验证,它允许您登录应用并发布内容。这个项目使用基于电子邮件的认证,这不同于前一章中的 Google 认证。

你需要回到火焰基地。点击认证选项卡,然后点击开始按钮,如图 5-7 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig7_HTML.jpg

图 5-7

开始

在下一个界面中,点击邮箱/密码的编辑图标,如图 5-8 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig8_HTML.jpg

图 5-8

电子邮件和密码

在弹出的窗口中,点击启用按钮,然后点击保存按钮,如图 5-9 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig9_HTML.jpg

图 5-9

启用电子邮件和密码

创建注册模式

现在,让我们展示一个来自 Material-UI 的注册模型。这个代码来自 https://material-ui.com/components/modal/#modal

首先,在App.js文件中导入几个依赖项和两个样式。在那之后,你就有了模态样式的常量。打开状态最初设置为

返回内部,将模态和注册按钮的打开状态设置为

更新的内容用粗体标记。

...
import { makeStyles } from '@material-ui/core/styles';
import Modal from '@material-ui/core/Modal';
import { Button, Input } from '@material-ui/core';
function getModalStyle() {
  const top = 50;
  const left = 50;
  return {
    top: `${top}%`,
    left: `${left}%`,
    transform: `translate(-${top}%, -${left}%)`,
  };
}
const useStyles = makeStyles((theme) => ({
  paper: {
    position: 'absolute',
    width: 400,
    backgroundColor: theme.palette.background.paper,
    border: '2px solid #000',
    boxShadow: theme.shadows[5],
    padding: theme.spacing(2, 4, 3),
  },
}));

function App() {
  const classes = useStyles();
  const [modalStyle] = React.useState(getModalStyle);
  const [open, setOpen] = useState(false)
  ...
  return (

    <div className="app">
      <Modal open={open} onClose={() => setOpen(false)}>
        <div style={modalStyle} className={classes.paper}>
          <h2>Modal Code</h2>
        </div>
      </Modal>
      <div className="app__header">...</div>
      <Button onClick={() => setOpen(true)}>Sign Up</Button>
        {posts.map(post => (
            <Post ={post.username} caption={post.caption} imageUrl={post.imageUrl} />
        ))}
    </div>
  );
}

export default App;

在 localhost 上,点击注册按钮,获得带文本的模态(见图 5-10 )。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig10_HTML.jpg

图 5-10

模式弹出菜单

在创建表单之前,您需要在App.js文件中创建三个状态变量——usernameemail,password

用户名、电子邮件和密码的字段在App.js文件的模式中。还有一个按钮包含一个调用signUp函数的onClick处理程序。

更新的内容用粗体标记。

...

function App() {
 ...
  const [username, setUsername] = useState('')
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  ...
  const signUp = e => {
    e.preventDefault()
  }

  return (
    <div className="app">
      <Modal open={open} onClose={() => setOpen(false)}>
        <div style={modalStyle} className={classes.paper}>
          <form className="app__signup">
              <center>
                <img className="app__headerImage" src="logo192.png"       alt="Header" />
              </center>
                <Input placeholder="username"
                  type="text"
                  value={username}
                  onChange={e => setUsername(e.target.value)}
                />

                <Input placeholder="email"
                  type="text"
                  value={email}
                  onChange={e => setEmail(e.target.value)}
                />
                <Input placeholder="password"
                  type="password"
                  value={password}
                  onChange={e => setPassword(e.target.value)}
                />
                <Button type="submit" onClick={signUp}>Sign Up</Button>
            </form>
        </div>
      </Modal>
      <div className="app__header">...</div>
      ...
    </div>
  );
}

export default App;

App.css文件中,为app__signup类添加样式。

.app__signup {
  display: flex;
  flex-direction: column;
}

5-11 显示点击 localhost 上的注册按钮打开一个表单。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig11_HTML.jpg

图 5-11

注册表单

向 Firebase 注册

让我们从用于身份验证的 Firebase 设置开始。首先,在photo-social-frontend文件夹中安装 Firebase 的所有依赖项。

npm i firebase

接下来,更新firebase.js文件以使用配置来初始化应用。更新的内容用粗体标记。

import firebase from 'firebase';

const firebaseConfig = {
    ...
};

const firebaseApp = firebase.initializeApp(firebaseConfig)
const db = firebaseApp.firestore()
const auth = firebase.auth()
const storage = firebase.storage()

export { db, auth, storage }

让我们为应用添加身份验证。首先,从本地 Firebase 导入 auth,然后在App.js文件中添加一个新的user状态变量。

向使用 Firebase 中的createUserWithEmailAndPassword并传递电子邮件和密码的signUp函数添加代码。之后,更新用户,将displayName设置为用户名。使用useEffect钩子来监控任何用户更改,并使用setUser()来更新user变量。

在返回界面中,检查用户是否登录,然后显示注销按钮或注册按钮。

更新的内容用粗体标记。

import { auth } from './firebase'
...

function App() {
 ...
  const [user, setUser] = useState(null)
  ...
  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged(authUser => {
      if(authUser) {
        console.log(authUser)
        setUser(authUser)
      } else {
        setUser(null)
      }
    })
    return () => {
      unsubscribe()
    }
  }, [user, username])
  const signUp = (e) => {
    e.preventDefault()
    auth.createUserWithEmailAndPassword(email, password)
      .then(authUser => authUser.user.updateProfile({ displayName: username }))
      .catch(error => alert(error.message))

    setOpen(false)
  }
  return (
    <div className="app">
      <Modal open={open} onClose={() => setOpen(false)}>...</Modal>
      <div className="app__header">...</div>
      {user ? <Button onClick={() => auth.signOut()}>Logout</Button> : <Button onClick={() => setOpen(true)}>Sign Up</Button>}
      ...
    </div>
  );
}

export default App;

身份验证在本地主机上正常工作。你可以注册一个新用户,如图 5-12 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig12_HTML.jpg

图 5-12

用户注册

使用 Firebase 登录

现在让我们通过在App.js文件中创建一个新的登录按钮和一个新的模态组件来处理登录功能。

首先,在App.js文件中创建openSignIn状态变量和函数。该函数包含来自 Firebase 的signInWithEmailAndPassword

注意,只使用了 email 和密码,但是有一个新的openSignIn状态变量和它的setOpenSignIn setter。更新的内容用粗体标记。

...
function App() {
 ...
  const [openSignIn, setOpenSignIn] = useState(false)
...
  const signIn = e => {
    e.preventDefault()
    auth.signInWithEmailAndPassword(email, password)
      .catch(error => alert(error.message))
    setOpenSignIn(false)
  }

  return (
    <div className="app">
      <Modal open={open} onClose={() => setOpen(false)}>...</Modal>
      <Modal open={openSignIn} onClose={() => setOpenSignIn(false)}>
        <div style={modalStyle} className={classes.paper}>
          <form className="app__signup">
            <center>
              <img className="app__headerImage" src="logo192.png" alt="Header" />
            </center>
              <Input placeholder="email" type="text" value={email}
                onChange={e => setEmail(e.target.value)}  />
              <Input placeholder="password" type="password" value={password}
                onChange={e => setPassword(e.target.value)}  />
              <Button type="submit" onClick={signIn}>Sign In</Button>
          </form>
        </div>
      </Modal>
      <div className="app__header">...</div>
      {user ? <Button onClick={() => auth.signOut()}>Logout</Button> :  (
          <div className="app__loginContainer">
            <Button onClick={() => setOpenSignIn(true)}>Sign In</Button>
            <Button onClick={() => setOpen(true)}>Sign Up</Button>
          </div>
        )}}
      ...
    </div>
  );
}

export default App;

localhost 上的按钮有了新的标志。它会打开一个弹出窗口来输入凭证(参见图 5-13 )。使用您为登录按钮输入的相同凭据,您可以成功登录。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig13_HTML.jpg

图 5-13

登录弹出窗口

添加帖子和图片

Firebase 用户身份验证已完成。添加帖子的代码并上传图片。一旦你开始后端,你就回到这个部分。

components文件夹中新建文件ImageUpload.jsImageUpload.css,并导入到App.js文件中。接下来,在App.js文件中传递来自ImageUpload的道具用户名。

App.js,中,创建一个具有app__posts类名的新 div,并在其中包含文章。App.js文件的更新内容用粗体标记。

...
import ImageUpload from './components/ImageUpload';
...
function App() {
...
  return (
    <div className="app">
        ...
        {user ? <Button onClick={() => auth.signOut()}>Logout</Button> :(
            ...
        )}
        <div className="app__posts">
          {posts.map(post => (
              <Post username={post.username} caption={post.caption} imageUrl={post.imageUrl} />
          ))}
        </div>
        {user?.displayName ? <ImageUpload username={user.displayName} /> : <h3 className="app__notLogin">Need to login to upload</h3>}
    </div>
  );
}

export default App;

ImageUpload.js文件中,从基本内容开始。有一个标题输入框和另一个图像输入框。还有一个按钮和一个进度条。

以下是ImageUpload.js文件的内容。

import React, { useState } from 'react'
import './ImageUpload.css'
const ImageUpload = ({ username }) => {
    const [image, setImage] = useState(null)
    const [progress, setProgress] = useState(0)
    const [caption, setCaption] = useState('')
const handleChange = e => {
        if(e.target.files[0]) {
            setImage(e.target.files[0])
        }
    }

const handleUpload = () => {}
    return (

        <div className="imageUpload">
            <progress className="imageUpload__progress" value={progress} max="100" />
            <input
                type="text"
                placeholder="Enter a caption..."
                className="imageUpload__input"
                value={caption}
                onChange={e => setCaption(e.target.value)}
            />
            <input className="imageUpload__file" type="file" onChange={handleChange} />
            <button className="imageUpload__button" onClick={handleUpload}>Upload</button>
        </div>
    )
}

export default ImageUpload

前端几乎完成,但你需要完成造型。首先,在ImageUpload.css文件中添加样式。以下是该文件的内容。

.imageUpload {
    display: flex;
    flex-direction: column;
    max-width: 800px;
    width: 100%;
    margin: 10px auto;
}

.imageUpload__progress{
    width: 100%;
    margin-bottom: 10px;
}

.imageUpload__input{
    padding: 10px;
    margin-bottom: 10px;
}

.imageUpload__file {

    margin-bottom: 10px;
}

.imageUpload__button {
    border: none;
    color: lightgray;
    background-color: #6082a3;
    cursor: pointer;
    padding: 10px;
    font-weight: bolder;
    font-size: 0.9rem;
}

.imageUpload__button:hover {
    color: #6082a3;
    background-color: lightgray;
}

5-14 显示了本地主机上的图像上传。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig14_HTML.jpg

图 5-14

图像上传

App.css文件中添加样式。更新后的代码用粗体标记。它保留了app__signupapp__headerImage的现有代码。

.app {
  display:grid;
  place-items: center;
  background-color: #fafafa;
}

.app__header{
  display: flex;
  justify-content: space-between;
  position: sticky;
  top: 0;
  z-index: 1;
  width: 100%;
  background-color: white;
  padding: 20px;
  border-bottom: 1px solid lightgray;
  object-fit: contain;
}

.app__notLogin{

  margin-bottom: 20px;
}

.app__loginContainer{
  margin-right: 10px;
}

.app__posts {
  padding: 20px;
}

App.js中有一个小的修正,将用户代码移动到app__header div 中。更新后的代码用粗体标记。

...
function App() {
...
  return (
    <div className="app">
      ...
      <div className="app__header">
        <img className="app__headerImage" src="logo192.png" alt="Header" />
        {user ? <Button onClick={() => auth.signOut()}>Logout</Button> :(
          <div className="app__loginContainer">
            <Button onClick={() => setOpenSignIn(true)}>Sign In</Button>
            <Button onClick={() => setOpen(true)}>Sign Up</Button>
          </div>
        )}
      </div>
     ...
    </div>
  );
}

export default App;

5-15 显示了本地主机上桌面视图中的应用。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig15_HTML.jpg

图 5-15

前端完成

初始后端设置

让我们转到后端,从 Node.js 代码开始。打开一个新的终端窗口,在根目录下创建一个新的photo-social-backend文件夹。移动到photo-social-backend目录后,输入git init命令,这是 Heroku 稍后需要的。

mkdir photo-social-backend
cd photo-social-backend
git init

接下来,通过在终端中输入npm init命令来创建package.json文件。你被问了一堆问题;对于大多数情况,只需按下回车键。你可以提供描述作者,但不是强制的。通常在标准的server.js,处设置进入点(见图 5-16 )。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig16_HTML.jpg

图 5-16

初始后端

一旦package.json被创建,您需要创建包含node_modules.gitignore文件,因为您不想以后将 node_modules 推送到 Heroku。以下是.gitignore文件内容。

node_modules

接下来,打开package.json.需要在 Node.js 中启用类似 React 的导入,包括一个启动脚本来运行server.js文件。更新的内容用粗体标记。

{
  "name": "messaging-app-backend",
  "version": "1.0.0",
  "description": "Messaging app backend",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Nabendu Biswas",
  "license": "ISC"
}

最后,您需要在启动之前安装两个软件包。打开终端,在photo-social-backend文件夹中安装 Express 和 Mongoose。

npm i express mongoose

MongoDB 设置

MongoDB 的设置与第 1 章中描述的相同。按照这些说明,创建一个名为 photo-social-mern 的新项目。

在继续之前,将nodemon安装在photo-social-backend文件夹中。它帮助 server.js 中的更改即时重启 Node 服务器。

npm i nodemon

初始路线设置

photo-social-backend文件夹中创建一个server.js文件。在这里,您导入 Express 和 Mongoose 包。然后使用 Express 创建一个运行在端口 9000 上的port变量。

第一个 API 端点是一个由app.get()创建的简单 GET 请求,如果成功,它会显示文本 Hello TheWebDev

然后用app.listen()监听端口。

import express from 'express'
import mongoose from 'mongoose'

//App Config
const app = express()
const port = process.env.PORT || 9000

//Middleware

//DB Config

//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))

//Listener
app.listen(port, () => console.log(`Listening on localhost: ${port}`))

在终端输入 nodemon server.js 查看监听 localhost: 9000 控制台日志。为了检查路线是否正常工作,转到http://localhost:9000/查看终点文本,如图 5-17 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig17_HTML.jpg

图 5-17

初始路线

数据库用户和网络访问

在 MongoDB 中,您需要创建一个数据库用户并授予网络访问权限。该过程与第 1 章中的解释相同。遵循这些说明,然后获取用户凭证和连接 URL。

server.js文件中,创建一个connection_url变量,并将 URL 粘贴到 MongoDB 的字符串中。您需要提供之前保存的密码和数据库名称。

更新后的代码用粗体标记。

...

//App Config
const app = express()
const port = process.env.PORT || 9000
const connection_url = ' mongodb+srv://admin:<password>@cluster0.giruc.mongodb.net/photoDB?retryWrites=true&w=majority'

//Middleware

//DB Config
mongoose.connect(connection_url, {
    useNewUrlParser: true,
    useCreateIndex: true,
    useUnifiedTopology: true
})

//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))

...

MongoDB 模式和路由

让我们为帖子创建一个模型。在photo-social-backend文件夹中创建一个postModel.js文件。

首先,用需要传递的参数创建一个模式,然后导出它。

import mongoose from 'mongoose'

const postsModel = mongoose.Schema({
    caption: String,
    user: String,
    image: String
})

export default mongoose.model('posts', postsModel)

现在,您可以使用该模式来创建向数据库添加数据的端点。

server.js中,创建一个到/upload端点的 POST 请求。负载在req.body到 MongoDB。然后使用create()发送dbPost.如果成功,您将收到状态 201;否则,您会收到状态 500。

接下来,创建/sync的 GET 端点,从数据库中获取数据。你在这里用的是find()。如果成功,您将收到状态 200(否则,状态 500)。

更新后的代码用粗体标记。

import express from 'express'
import mongoose from 'mongoose'
import Posts from './postModel.js'
...

//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))

app.post('/upload', (req, res) => {
    const dbPost = req.body
    Posts.create(dbPost, (err, data) => {
        if(err)
            res.status(500).send(err)
        else
            res.status(201).send(data)
    })
})

app.get('/sync', (req, res) => {
    Posts.find((err, data) => {
        if(err) {
            res.status(500).send(err)
        } else {
            res.status(200).send(data)
        }
    })
})

//Listener
app.listen(port, () => console.log(`Listening on localhost: ${port}`))

在处理 POST 请求之前,您需要完成两件事情。第一,实行 First 否则,当您稍后部署应用时,会出现跨来源错误。打开终端,在photo-social-backend文件夹中安装 CORS。

npm i cors

server.js中,导入 CORS,然后配合app.use()使用。你还需要使用express.json()中间件。更新后的代码用粗体标记。

import express from 'express'
import mongoose from 'mongoose'
import Cors from 'cors'
import Posts from './postModel.js'

...

//Middleware
app.use(express.json())
app.use(Cors())

...

在 Postman 中,您需要将请求更改为 POST,然后添加http://localhost:9000/upload端点。

之后,点击正文然后选择 raw 。从下拉菜单切换到 JSON(应用/json) 。在文本编辑器中,输入如图 5-18 所示的数据。要改变的一件事是通过给键加上双引号来使数据 JSON。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig18_HTML.jpg

图 5-18

邮递员邮件

接下来,点击发送按钮。如果一切正确,你得到状态:201 已创建,如图 5-18 所示。

我以类似的方式插入了其他数据。您需要测试 GET /sync端点。将请求更改为 GET,然后单击发送按钮。如果一切正确,你得到状态:200 OK ,如图 5-19 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig19_HTML.jpg

图 5-19

邮递员得到

有时在发布请求时,服务器会出错。错误为UnhandledPromiseRejectionWarning:MongooseServerSelectionError:connection

如果您遇到此错误,请转到您的网络访问选项卡,并点击添加 IP 地址按钮。之后点击添加当前 IP 地址按钮,点击确认,如图 5-20 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig20_HTML.jpg

图 5-20

添加当前 IP

将后端与前端集成在一起

你想在应用初始加载时获取所有消息,然后推送消息。您需要达到 GET 端点,为此您需要 Axios。打开photo-social-frontend文件夹并安装。

npm i axios

接下来,在src文件夹中创建一个新的axios.js文件,然后创建一个axios的实例。基础 URL 是http://localhost:9000

import axios from 'axios'

const instance = axios.create({
    baseURL: "http://localhost:9000"
})

export default instance

ImageUpload.js文件中,从 Firebase 和 Axios 导入存储。更新handleUpload(),点击上传按钮后触发。

首先,在uploadTask变量中取上传的图片路径,放入数据库。检查state_changed因为快照改变了。根据上传的 has 数量,更新setProgress中的进度条。

之后,你需要做错误管理。从 Firebase 获取图像 URL。

接下来,获取标题、用户名和 URL,并在 MongoDB 中执行axios.post/upload的操作。

更新后的代码用粗体标记。

...
import { storage } from "../firebase";
import axios from '../axios'
const ImageUpload = ({ username }) => {
    ...
    const [url, setUrl] = useState("");
    const handleChange = e => {...}
    const handleUpload = () => {
        const uploadTask = storage.ref(`img/${image.name}`).put(image);
        uploadTask.on(
            "state_changed",
            (snapshot) => {
                const progress = Math.round(
                    (snapshot.bytesTransferred / snapshot.totalBytes) * 100
                );
                setProgress(progress);
            },
            (error) => {
                console.log(error);
            },
            () => {
                storage
                    .ref("images")
                    .child(image.name)
                    .getDownloadURL()
                    .then((url) => {
                        setUrl(url);
                        axios.post('/upload', {
                            caption: caption,
                            user: username,
                            image: url
                        })
                        setProgress(0);
                        setCaption("");
                        setImage(null);
                    });
            }
        );
    };
    return (...)
}

export default ImageUpload

在测试之前,您需要在 Firebase 控制台中设置存储。首先点击存储选项卡,然后点击开始按钮,弹出如图 5-21 所示的窗口。然后,点击下一个按钮。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig21_HTML.jpg

图 5-21

燃料库

在下一个画面中,点击完成按钮,如图 5-22 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig22_HTML.jpg

图 5-22

云存储

进入本地主机,上传任何图片,输入标题,点击上传按钮。你可以看到帖子被保存到 MongoDB(见图 5-23 )。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig23_HTML.jpg

图 5-23

蒙戈布省省市镇

App.js中,你需要从 MongoDB 中获取帖子。首先,导入本地axios。然后创建一个新的useEffect钩子,并向/sync端点发出 GET 请求。

接下来,用从 MongoDB 收到的数据更新App.js

更新后的代码用粗体标记。

...
import axios from './axios'
...
function App() {
  ...
  const fetchPosts = async () => {
    await axios.get("/sync").then(response => setPosts(response.data))
  }
  useEffect(() => {
    fetchPosts()
  },[])
  ...
  return (
    <div className="app">
        ...
        <div className="app__posts">
          {posts.map(post => (
            <Post
              key={post._id}
              username={post.user}
              caption={post.caption}
              imageUrl={post.image}
            />
          ))}
        </div>

        ...
    </div>
  );
}

export default App;

5-24 显示了来自本地主机上的 MongoDB 数据库的 post。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig24_HTML.jpg

图 5-24

来自 MongoDB 的帖子

配置推动器

既然 MongoDB 不是实时数据库,那就该给 app 加一个 pusher 来获取实时数据了。因为你已经完成了第四章的设置,按照同样的说明,创建一个名为的应用。

将推杆添加到后端

同样,您需要停止服务器并安装 Pusher。在photo-social-backend文件夹中,用下面的命令安装它。

npm i pusher

server.js文件中,导入它,然后使用推动器初始化代码。从 Pusher 网站获取初始化代码( https://pusher.com )。要添加代码,用db.once打开一个数据库连接。然后用watch()观看来自 MongoDB 的消息集合。

changeStream里面,如果operationType被插入,你把数据插入到推动器里。更新后的代码用粗体标记。

...
import Pusher from 'pusher'
...
//App Config
...
const pusher = new Pusher({
    appId: "11xxxx",
    key: "9exxxxxxxxxxxxx",
    secret: "b7xxxxxxxxxxxxxxx",
    cluster: "ap2",
    useTLS: true
});

//API Endpoints
mongoose.connect(connection_url, {  ...})

mongoose.connection.once('open', () => {
    console.log('DB Connected')
    const changeStream = mongoose.connection.collection('posts').watch()
    changeStream.on('change', change => {
        console.log(change)
        if(change.operationType === "insert") {
            console.log('Trigerring Pusher')
            pusher.trigger('posts','inserted', {
                change: change
           })
        } else {
            console.log('Error trigerring Pusher')
        }
    })
})

app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))
...
//Listener
app.listen(port, () => console.log(`Listening on localhost: ${port}`))

为了测试这一点,您需要从前端上传一个新的图像。同时,你需要在调试控制台中推料。

5-25 显示了调试控制台日志中显示的消息。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig25_HTML.jpg

图 5-25

推进计程仪

将推杆添加到前端

是时候移动到前端使用 Pusher 了。首先,你需要在photo-social-frontend文件夹中安装pusher-js包。

npm i pusher-js

www.pusher.com 获取代码放入 app 前端。导入推动器,然后使用App.js文件中的代码,这里有一个新的用于推动器的useEffect()钩。更新的内容用粗体标记。

...
import Pusher from 'pusher-js'

const pusher = new Pusher('56xxxxxxxxxxxxxxxx', {
  cluster: 'ap2'
});

function App() {
  ...
  const fetchPosts = async () => {
    await axios.get("/sync").then(response => setPosts(response.data))
  }

  useEffect(() => {
    const channel = pusher.subscribe('posts');
    channel.bind('inserted', (data) => {
      fetchPosts()
    });
  }, [])

  useEffect(() => {
    fetchPosts()
  },[])
  ...
  return (
    <div className="app">
      ...
    </div>
  );
}

export default App;

去找邮递员并发送另一个邮寄请求。您可以在本地主机上看到控制台日志中的数据。应用已完成。无论何时你发布了什么,它都会实时显示出来。

隐藏秘密

您可以在将应用部署到 Heroku 或推送到 GitHub 之前隐藏秘密,这是一种最佳做法。使用以下命令将dotenv安装到photo-social-backend文件夹中。

npm i dotenv

然后在photo-social-backend文件夹中创建一个.env文件,并将所有秘密添加到其中。

DB_CONN='mongodb+srv://admin:<password>@cluster0.giruc.mongodb.net/photoDB?retryWrites=true&w=majority'
PUSHER_ID="11xxxx"
PUSHER_KEY="56xxxxxxxxxxxxxxxxxx"
PUSHER_SECRET="90xxxxxxxxxxxxxxxxxxx"

server.js中,导入dotenv,然后使用其中的值来代替所有的秘密。

...
import Posts from './postModel.js'
import dotenv from 'dotenv';

//App Config
dotenv.config()
const app = express()
const port = process.env.PORT || 9000
const connection_url = process.env.DB_CONN

const pusher = new Pusher({
    appId: process.env.PUSHER_ID,
    key: process.env.PUSHER_KEY,
    secret: process.env.PUSHER_SECRET,
    cluster: "ap2",
    useTLS: true
});

//Middleware
...

在后端的.gitignore文件中添加.env文件。更新的内容用粗体标记。

node_modules
.env

将后端部署到 Heroku

转到 www.heroku.com 部署后端。按照第 1 章的步骤创建一个名为照片-社交-后台的应用。

由于这次您有环境变量,您必须将它们添加到设置➤配置变量中。请注意,不要在按键周围加上任何引号,如图 5-26 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig26_HTML.jpg

图 5-26

Heroku 的环境变量

部署成功后,进入 https://photo-social-backend.herokuapp.com 。图 5-27 显示了正确的文本。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig27_HTML.jpg

图 5-27

后端已部署

转到axios.js,将端点改为 https://photo-social-backend.herokuapp.com 。如果一切正常,你的应用应该可以运行了。

import axios from 'axios'
const instance = axios.create({

    baseURL: " https://photo-social-backend.herokuapp.com "
})

export default instance

将前端部署到 Firebase

是时候在 Firebase 中部署前端了。遵循与第 1 章相同的程序。完成此过程后,站点应处于活动状态并正常工作,如图 5-28 所示。

img/512020_1_En_5_Chapter/512020_1_En_5_Fig28_HTML.jpg

图 5-28

最终应用

摘要

在这一章中,你创建了一个简单而实用的基于照片的社交网络。Firebase 在网上主办的。您学习了添加电子邮件身份验证,通过它您可以使用电子邮件登录。您还了解了如何在 Firebase 中存储图像,以及如何使用 Node.js 创建的 API 路由在 MongoDB 数据库中存储图像和文章的链接。