简介
MERN堆栈由MongoDB、Express、Reaction/Redux和Node.js组成。MERN堆栈是用于构建现代单页面Web应用程序的最流行的JavaScript堆栈之一。
在本教程中,您将构建一个使用RESTful应用编程接口的TODO应用程序 ,您也将在本教程的后面构建该应用程序。
的动画屏幕截图
前提条件
要完成本教程,您需要:
- 本地安装Node.js,可以按照如何安装Node.js并创建本地开发环境操作。
<$>[备注] 注意: 本教程的初衷是使用mLab服务托管MongoDB数据库。MLab自2019年2月以来一直禁止创建新账户,他们建议使用MongoDB Atlas。
也可以按照安装instructions]在本地运行mongodb,但本教程不会介绍该过程,只是出于教育目的而保留原样。 <$>
您还需要一个熟悉的代码编辑器,最好是支持Java代码突出显示的编辑器。
建议下载并安装像Postman这样的工具来测试API端点。
本教程通过Node v14.2.0、npm
v6.14.5、mongob-Community
v4.2.6、exples
v4.17.1、mongoose
v5.9.17进行验证。
第一步-设置应用程序
让我们从设置开始。打开您的终端,在本地计算机上的任何方便位置创建一个新的文件目录。你可以给它起任何名字,但在本例中,它被称为mern-todo
。
1mkdir mern-todo
现在,进入该文件目录:
1cd mern-todo
下一步是使用Package.json
文件初始化项目。此文件将包含有关您的应用程序及其运行所需依赖项的一些信息。
您可以使用:
1npm init
并在出现提示时按照说明操作。或者,您可以使用:
1npm init -y
使用默认值。
第二步-设置节点服务器
要在后端运行您的Java代码,您需要启动一个服务器来编译您的代码。
可以通过两种方式创建服务器:一是使用Node中内置的Http
模块;二是使用Express.js框架.
本教程将使用Express.js。它是一个Node.js HTTP框架,可以处理很多开箱即用的事情,只需要很少的代码就可以创建功能齐全的REST风格的API。要使用Express,请使用NPM进行安装:
1npm install express
现在,创建一个文件index.js
,并在其中输入以下代码并保存:
1[label index.js]
2const express = require('express');
3
4const app = express();
5
6const port = process.env.PORT || 5000;
7
8app.use((req, res, next) => {
9 res.header('Access-Control-Allow-Origin', '*');
10 res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
11 next();
12});
13
14app.use((req, res, next) => {
15 res.send('Welcome to Express');
16});
17
18app.listen(port, () => {
19 console.log(`Server running on port ${port}`);
20});
前面代码中的这段代码有助于处理您在开发和测试期间尝试从不同域访问API时可能会遇到的CORS相关问题:
1app.use((req, res, next) => {
2 res.header('Access-Control-Allow-Origin', '*');
3 res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
4 next();
5});
现在是启动服务器的时候了,看看它是否工作。在您的index.js
文件所在的目录中打开您的终端,然后键入:
1node index.js
如果一切正常,您会在终端中看到服务器在5000端口上运行 。
第三步-创建路由
这款应用程序需要做三件事:
- 创建任务
- 查看所有任务
- 删除已完成的任务
对于每个任务,您将需要创建定义TODO应用程序将依赖的多个端点的路由。因此,让我们创建一个routes
文件夹,并创建一个包含以下代码的文件api.js
。
1mkdir routes
编辑api.js
并粘贴以下代码:
1[label routes/api.js]
2const express = require('express');
3const router = express.Router();
4
5router.get('/todos', (req, res, next) => {
6 // get placeholder
7});
8
9router.post('/todos', (req, res, next) => {
10 // post placeholder
11});
12
13router.delete('/todos/:id', (req, res, next) => {
14 // delete placeholder
15});
16
17module.exports = router;
这为GET、POST和DELETE提供了占位符路由。
第四步-定义模型
现在,有趣的部分来了。由于应用程序将使用MongoDB),这是一个NoSQL数据库,我们需要创建一个_MODEL_和一个_SCHEMA_。使用模式接口定义模型。该模式允许您定义存储在每个文档中的字段及其验证要求和默认值。从本质上讲,该模式是如何构建数据库的蓝图。此外,您还可以定义静态和实例帮助器方法,以便更容易地使用数据类型以及虚拟属性,这些属性可以像使用任何其他字段一样使用,但不存储在数据库中。
要创建模式和模型,请安装Mongoose,这是一个节点包,可以更轻松地使用MongoDB。
1# ensure that you are in the `mern-todo` project directory
2npm install mongoose
在您的根目录中创建一个新文件夹,并将其命名为Models
。在其中创建一个文件并将其命名为todo.js
,其中包含以下代码:
1mkdir models
使用文本编辑器将以下内容粘贴到todo.js
中:
1[label models/todo.js]
2const mongoose = require('mongoose');
3const Schema = mongoose.Schema;
4
5// Create schema for todo
6const TodoSchema = new Schema({
7 action: {
8 type: String,
9 required: [true, 'The todo text field is required'],
10 },
11});
12
13// Create model for todo
14const Todo = mongoose.model('todo', TodoSchema);
15
16module.exports = Todo;
现在,我们需要更新我们的路线,以使用新的模式。
1[label routes/api.js]
2const express = require('express');
3const router = express.Router();
4const Todo = require('../models/todo');
5
6router.get('/todos', (req, res, next) => {
7 // This will return all the data, exposing only the id and action field to the client
8 Todo.find({}, 'action')
9 .then((data) => res.json(data))
10 .catch(next);
11});
12
13router.post('/todos', (req, res, next) => {
14 if (req.body.action) {
15 Todo.create(req.body)
16 .then((data) => res.json(data))
17 .catch(next);
18 } else {
19 res.json({
20 error: 'The input field is empty',
21 });
22 }
23});
24
25router.delete('/todos/:id', (req, res, next) => {
26 Todo.findOneAndDelete({ _id: req.params.id })
27 .then((data) => res.json(data))
28 .catch(next);
29});
30
31module.exports = router;
第五步-连接数据库
您需要一个数据库来存储您的数据。为此,您将使用mLab.按照The documentation]开始使用mLab。
设置数据库后,您需要使用以下代码更新index.js
文件:
1[label index.js]
2const express = require('express');
3const bodyParser = require('body-parser');
4const mongoose = require('mongoose');
5const routes = require('./routes/api');
6require('dotenv').config();
7
8const app = express();
9
10const port = process.env.PORT || 5000;
11
12// Connect to the database
13mongoose
14 .connect(process.env.DB, { useNewUrlParser: true })
15 .then(() => console.log(`Database connected successfully`))
16 .catch((err) => console.log(err));
17
18// Since mongoose's Promise is deprecated, we override it with Node's Promise
19mongoose.Promise = global.Promise;
20
21app.use((req, res, next) => {
22 res.header('Access-Control-Allow-Origin', '*');
23 res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
24 next();
25});
26
27app.use(bodyParser.json());
28
29app.use('/api', routes);
30
31app.use((err, req, res, next) => {
32 console.log(err);
33 next();
34});
35
36app.listen(port, () => {
37 console.log(`Server running on port ${port}`);
38});
<$>[注]
注意: 在Express 4.16+之前的版本中,必须依赖于中间件,如body-parser
。但是,现在可以使用内置解析器。
如果您使用的是旧版本的Express,请使用NPM安装body-parser
:
1npm install body-parser
<$>
在前面的代码中,使用了cess.env
来访问需要创建的环境变量。在您的根目录下创建一个名为.env
的文件,然后编辑:
1[label .env]
2DB = 'mongodb://<USER>:<PASSWORD>@example.mlab.com:port/todo'
在创建数据库和用户之后,请确保使用来自mLab的MongoDB URL。将<user>
替换为您创建的用户名,并将<password>
替换为您创建的用户的密码。
要使用环境变量,您必须安装一个名为dotenv
的节点包,该包确保您有权访问存储在.env
文件中的环境变量。
1# ensure that you are in the `mern-todo` project directory
2npm install dotenv
然后在index.js
中请求并配置:
1require('dotenv').config()
使用环境变量而不是直接将凭据写入应用程序代码可能会对您的版本控制系统隐藏敏感信息。以这种方式将配置和机密数据与应用程序代码分开被认为是最佳实践。
Step 6-测试接口
这是我们开始尝试的部分,以确保您的RESTful API工作正常。由于您的前端还没有准备好,您可以使用一些API开发客户端来测试您的代码。
使用以下命令启动服务器:
1node index.js
现在,打开您的客户端,创建一个Get方法并导航到http://localhost:5000/api/todos
.
的屏幕截图
测试所有API端点并确保它们工作正常。对于需要`Body‘的端点,将JSON与必要的字段一起发回,因为它是您在代码中设置的。
发布请求示例:
1POST localhost:5000/api/todos
2Body
3raw
帖子值示例:
1{
2 "action": "build a mern stack application"
3}
GET请求示例:
1GET localhost:5000/api/todos
Get Response示例:
1[secondary_label Output]
2[
3 {
4 "id": "5bd4edfc89d4c3228e1bbe0a",
5 "action": "build a mern stack application"
6 }
7]
删除请求示例:
1DELETE localhost:5000/api/todos/5bd4edfc89d4c3228e1bbe0ad
测试并观察GET、POST和DELETE的结果。
Step 7 -创建前端
由于您已经完成了想要从API获得的功能,现在是时候为客户端创建一个与API交互的接口了。要开始使用TODO应用程序的前端,您将使用create-react-app
命令来构建您的应用程序。
在与后端代码相同的根目录(即mern-todo
目录)中,运行:
1npx create-react-app client
这将在你的mern-todo
目录中创建一个名为client
的新文件夹,在那里你将添加所有的React代码。
Step 8-运行Reaction App
在测试Reaction应用程序之前,需要在项目根目录中安装许多依赖项。
首先,将concurrently
安装为开发依赖项:
1npm install concurrently --save-dev
并发用于从同一终端窗口同时运行多个命令。
然后安装nodemon
作为开发依赖:
1npm install nodemon --save-dev
Nodemon用于运行服务器并监视它。如果服务器代码有任何更改,Nodemon将使用新的更改自动重新启动它。
接下来,打开APP项目根文件夹中的Package.json
文件,粘贴以下代码:
1[label package.json]
2{
3 // ...
4 "scripts": {
5 "start": "node index.js",
6 "start-watch": "nodemon index.js",
7 "dev": "concurrently \"npm run start-watch\" \"cd client && npm start\""
8 },
9 // ...
10}
进入客户端文件夹,找到Package.json
文件,并在其中添加以下键值对。
1[label client/package.json]
2{
3 // ...
4 "proxy": "http://localhost:5000"
5}
我们的Package.json
文件中的这个代理设置将使您能够进行API调用,而无需输入完整的URL,只需输入/api/todos
即可获得您的所有待办事项
打开你的终端,运行npm run dev
,并确保你在todo
目录中,而不是在client
目录中。
您的应用程序将打开并在localhost:3000
上运行。
步骤9-创建Reaction组件
Reaction的优势之一是它利用了组件,这些组件是可重用的,并且还使代码模块化。对于您的TODO应用程序,将有两个状态组件和一个无状态组件。
在您的src
文件夹中创建另一个名为Components
的文件夹,并在其中创建三个文件Input.js
、ListTodo.js
和Todo.js
。
打开Input.js
文件,粘贴如下内容:
1[label client/src/components/Input.js]
2import React, { Component } from 'react';
3import axios from 'axios';
4
5class Input extends Component {
6 state = {
7 action: '',
8 };
9
10 addTodo = () => {
11 const task = { action: this.state.action };
12
13 if (task.action && task.action.length > 0) {
14 axios
15 .post('/api/todos', task)
16 .then((res) => {
17 if (res.data) {
18 this.props.getTodos();
19 this.setState({ action: '' });
20 }
21 })
22 .catch((err) => console.log(err));
23 } else {
24 console.log('input field required');
25 }
26 };
27
28 handleChange = (e) => {
29 this.setState({
30 action: e.target.value,
31 });
32 };
33
34 render() {
35 let { action } = this.state;
36 return (
37 <div>
38 <input type="text" onChange={this.handleChange} value={action} />
39 <button onClick={this.addTodo}>add todo</button>
40 </div>
41 );
42 }
43}
44
45export default Input;
要使用axios,它是浏览器和Node.js的基于Promise的HTTP客户端,您需要从您的终端导航到您的client
目录:
1cd client
然后运行npm install axios
:
1npm install axios
然后,打开您的ListTodo.js
文件并粘贴以下代码:
1[label client/src/components/ListTodo.js]
2import React from 'react';
3
4const ListTodo = ({ todos, deleteTodo }) => {
5 return (
6 <ul>
7 {todos && todos.length > 0 ? (
8 todos.map((todo) => {
9 return (
10 <li key={todo._id} onClick={() => deleteTodo(todo._id)}>
11 {todo.action}
12 </li>
13 );
14 })
15 ) : (
16 <li>No todo(s) left</li>
17 )}
18 </ul>
19 );
20};
21
22export default ListTodo;
然后,在Todo.js
文件中编写以下代码:
1[label client/src/components/Todo.js]
2import React, { Component } from 'react';
3import axios from 'axios';
4import Input from './Input';
5import ListTodo from './ListTodo';
6
7class Todo extends Component {
8 state = {
9 todos: [],
10 };
11
12 componentDidMount() {
13 this.getTodos();
14 }
15
16 getTodos = () => {
17 axios
18 .get('/api/todos')
19 .then((res) => {
20 if (res.data) {
21 this.setState({
22 todos: res.data,
23 });
24 }
25 })
26 .catch((err) => console.log(err));
27 };
28
29 deleteTodo = (id) => {
30 axios
31 .delete(`/api/todos/${id}`)
32 .then((res) => {
33 if (res.data) {
34 this.getTodos();
35 }
36 })
37 .catch((err) => console.log(err));
38 };
39
40 render() {
41 let { todos } = this.state;
42
43 return (
44 <div>
45 <h1>My Todo(s)</h1>
46 <Input getTodos={this.getTodos} />
47 <ListTodo todos={todos} deleteTodo={this.deleteTodo} />
48 </div>
49 );
50 }
51}
52
53export default Todo;
您需要对您的反应代码进行一些调整。删除标识并调整您的App.js
,如下所示:
1[label client/src/App.js]
2import React from 'react';
3import Todo from './components/Todo';
4import './App.css';
5
6const App = () => {
7 return (
8 <div className="App">
9 <Todo />
10 </div>
11 );
12};
13
14export default App;
然后将以下代码粘贴到App.css
中:
1[label client/src/App.css]
2.App {
3 text-align: center;
4 font-size: calc(10px + 2vmin);
5 width: 60%;
6 margin-left: auto;
7 margin-right: auto;
8}
9
10input {
11 height: 40px;
12 width: 50%;
13 border: none;
14 border-bottom: 2px #101113 solid;
15 background: none;
16 font-size: 1.5rem;
17 color: #787a80;
18}
19
20input:focus {
21 outline: none;
22}
23
24button {
25 width: 25%;
26 height: 45px;
27 border: none;
28 margin-left: 10px;
29 font-size: 25px;
30 background: #101113;
31 border-radius: 5px;
32 color: #787a80;
33 cursor: pointer;
34}
35
36button:focus {
37 outline: none;
38}
39
40ul {
41 list-style: none;
42 text-align: left;
43 padding: 15px;
44 background: #171a1f;
45 border-radius: 5px;
46}
47
48li {
49 padding: 15px;
50 font-size: 1.5rem;
51 margin-bottom: 15px;
52 background: #282c34;
53 border-radius: 5px;
54 overflow-wrap: break-word;
55 cursor: pointer;
56}
57
58@media only screen and (min-width: 300px) {
59 .App {
60 width: 80%;
61 }
62
63 input {
64 width: 100%
65 }
66
67 button {
68 width: 100%;
69 margin-top: 15px;
70 margin-left: 0;
71 }
72}
73
74@media only screen and (min-width: 640px) {
75 .App {
76 width: 60%;
77 }
78
79 input {
80 width: 50%;
81 }
82
83 button {
84 width: 30%;
85 margin-left: 10px;
86 margin-top: 0;
87 }
88}
同样在index.css
中添加以下规则:
1[label client/src/index.css]
2body {
3 margin: 0;
4 padding: 0;
5 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
6 -webkit-font-smoothing: antialiased;
7 -moz-osx-font-smoothing: grayscale;
8 box-sizing: border-box;
9 background-color: #282c34;
10 color: #787a80;
11}
12
13code {
14 font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
15}
假设保存所有这些文件时没有错误,TODO应用程序将准备就绪,并具有前面讨论的功能:创建任务、删除任务和查看所有任务。
总结
在本教程中,您使用MERN堆栈创建了一个ToDo应用程序。您使用Reaction编写了一个前端应用程序,该应用程序与使用Express.js编写的后端应用程序进行通信。您还创建了一个MongoDB后端,用于在数据库中存储任务。