I'm a tabletop game developer, and enjoy making apps that have the potential to perform some service related to gaming. In this article, we'll walk through the steps to create a roleplaying game character generator using MongoDB, Express, Vue, and Node (also known as the "MEVN" stack).
我是一名桌上游戏开发人员,喜欢制作能够执行某些与游戏相关的服务的应用程序。 在本文中,我们将逐步完成使用MongoDB , Express , Vue和Node (也称为“ MEVN”堆栈)创建角色扮演游戏角色生成器的步骤。
Prerequisites: this tutorial presumes that you have Node/NPM and MongoDB installed and configured, with a code editor and CLI (or IDE) ready to go.
先决条件:本教程假定您已安装并配置了Node / NPM和MongoDB,并且已准备好使用代码编辑器和CLI (或IDE )。
If you'd prefer to follow along with a visual tutorial, you can check out the companion video to this article below:
如果您希望遵循视觉教程,可以在下面查看本文的配套视频:
I should also mention that this tutorial would not have been possible without Bennett Dungan's article on building a REST API, Aneeta Sharma's tutorial on full stack MEVN web apps, and Matt Maribojoc's article on the same topic.
我还应该提到,如果没有Bennett Dungan的文章有关构建REST API的文章,Aneeta Sharma的关于全栈式MEVN Web应用程序的文章以及Matt Matterbojoc的关于同一主题的文章,就不可能实现本教程。
I used each of these articles in addition to official documentation (for Vue, Express, and a whole lot more) in learning to create my own MEVN apps (you can read more about my journey with web APIs here).
除了学习官方文档(适用于Vue , Express以及更多内容)外,我还使用了所有这些文章来学习创建自己的MEVN应用程序(您可以在此处阅读有关Web API的更多信息)。
You can access the entire repository for this tutorial on GitHub.
您可以在GitHub上访问本教程的整个存储库。
前端 (The Front End)
Our app is going to allow us to create new roleplaying game characters and view them altogether, with the following stack:
我们的应用程序将允许我们使用以下堆栈来创建新的角色扮演游戏角色并一起查看它们:
- Vue Client Vue客户
- Node/Express Server 节点/快速服务器
- MongoDB Database MongoDB数据库
The Vue Client will make HTTP requests to the Node/Express Server (or "API"), which will in turn communicate with our MongoDB Database to send data back up the stack.
Vue客户端将向Node / Express Server(或“ API ”)发出HTTP请求 ,后者再与我们的MongoDB数据库进行通信,以将数据发送回堆栈。
We'll begin by opening a command line, creating a new directory for our project, and navigating into that directory:
我们将首先打开命令行,为我们的项目创建一个新目录,然后导航到该目录:
mkdir mevn-character-generator
cd mevn-character-generator
We'll then install the Vue CLI globally to help us scaffold a basic app:
然后,我们将在全球范围内安装Vue CLI ,以帮助我们搭建基本应用程序:
npm install -g @vue/cli
Next, we'll use the Vue CLI to create a new app called "Client" within our mevn-character-generator directory:
接下来,我们将使用Vue CLI在mevn-character-generator目录中创建一个名为“ Client”的新应用程序:
vue create client
You can just hit "enter" at the prompt to keep going.
您可以在提示下按“输入”以继续操作。
We can run our app by first navigating into the /client folder:
我们可以先导航到/ client文件夹来运行我们的应用程序:
cd client
npm run serve
When the script has completed running, we can now open a browser page and navigate to the URL indicated by our terminal (usually http://localhost:8080 or 8081). We should see something like this:
脚本运行完毕后,我们现在可以打开浏览器页面并导航到终端指示的URL(通常为http:// localhost:8080或8081)。 我们应该看到这样的东西:
Nice! The Vue CLI has scaffolded a basic app for us, and is rendering it right into the browser. It'll also reload the page automatically upon file changes, and throw errors if something in the code looks amiss.
真好! Vue CLI为我们搭建了一个基本应用程序,并将其直接呈现到浏览器中。 它还会在文件更改后自动重新加载页面,如果代码中的某些内容不正确,则会引发错误。
Let's open the project directory in our code editor to take a look at the file structure, which should look like this:
让我们在代码编辑器中打开项目目录,以查看文件结构,该文件结构应如下所示:
If you're OCD like I am, you can go ahead and delete the "favicon.ico" file and "/assets" folder as we won't need them for this project.
如果您像我一样是OCD,则可以继续删除“ favicon.ico”文件和“ / assets”文件夹,因为我们在此项目中不需要它们。
Diving into /src/main.js, we see:
深入到/src/main.js,我们看到:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
This file is the main entry point for our client. It tells the browser to mount our App.vue file to the div with id "#app" in /public/index.html.
该文件是我们客户的主要入口点。 它告诉浏览器将我们的App.vue文件安装到/public/index.html中ID为“ #app”的div上。
Let's look at /src/App.vue (I've omitted some code for readability):
让我们看一下/src/App.vue(为了便于阅读,我省略了一些代码):
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
...
}
</style>
App.vue is a typical Vue component, with <template>, <script>, and <style> tags.
App.vue是典型的Vue 组件 ,带有<template>,<script>和<style>标签。
The section between the <template> tags is the HTML that we see rendered on the screen. Within, we see a reference to the image that we deleted, and a <HelloWorld/> component that is being fed the message "Welcome to Your Vue.js App."
<template>标记之间的部分是我们在屏幕上看到HTML。 在其中,我们看到了对已删除图像的引用,以及一个正在收到消息“欢迎使用您的Vue.js应用程序”的<HelloWorld />组件。
The <script> section imports other components that are in use, and exports any data that we want to include in our app. Note that in App.vue, we're importing HelloWorld.vue from another directory, and exporting it as a component so that our main.js can have access to it.
<script>部分将导入正在使用的其他组件,并导出要包含在应用程序中的所有数据。 请注意,在App.vue中,我们从另一个目录导入HelloWorld.vue,并将其导出为组件,以便我们的main.js可以访问它。
The <style> tags are for your own brilliant and vibrant CSS, which we won't be using for this tutorial (womp womp).
<style>标记用于您自己的绚丽而充满活力CSS,我们将不在本教程中使用(womp womp)。
Let's follow the thread to /src/components/HelloWorld.vue:
让我们跟随线程到/src/components/HelloWorld.vue:
HelloWorld.vue follows a similar component structure to App.vue. It expects to receive the props "msg" as a String from the parent component that calls it (which is in this case App.vue). HelloWorld.vue then serves the message directly into the HTML template between the curly braces as {{msg}}.
HelloWorld.vue遵循与App.vue相似的组件结构。 它期望从调用它的父组件(在本例中为App.vue)中以字符串形式接收道具 “ msg”。 然后,HelloWorld.vue将消息作为{{msg}}直接送入花括号之间HTML模板中。
It's also important to note that the <style> tags here (which we're still not using) are scoped, meaning that if you wanted to apply CSS to this component alone, you could do so.
同样重要的是要注意,这里的<style>标签(我们仍未使用)是作用域的,这意味着如果您想仅将CSS应用于此组件,则可以这样做。
Let's delete all the HTML in HelloWorld.vue and change the name of the file to "CharacterViewer.vue." Update the code to:
让我们删除HelloWorld.vue中的所有HTML,并将文件名更改为“ CharacterViewer.vue”。 将代码更新为:
<template>
<div class="character-viewer">
<h1>Character Viewer</h1>
</div>
</template>
<script>
export default {
name: 'CharacterViewer'
}
</script>
<style scoped>
</style>
That's much simpler, but it requires us to change all references to "HelloWorld" in App.vue:
这要简单得多,但是它要求我们更改App.vue中对“ HelloWorld”的所有引用:
<template>
<div id="app">
<CharacterViewer />
</div>
</template>
<script>
import CharacterViewer from './components/CharacterViewer.vue'
export default {
name: 'App',
components: {
CharacterViewer
}
}
</script>
The Vue CLI, which may have been throwing you errors while deleting and re-arranging stuff, should reload. If you check out your browser again, you'll see:
应该重新加载Vue CLI,该Vue CLI在删除和重新排列内容时可能会向您抛出错误。 如果您再次签出浏览器,则会看到:
Pretty exciting. Let's add a "Character Creator" component by duplicating CharacterViewer.vue and calling it "CharacterCreator.vue", replacing the code:
非常令人兴奋。 让我们通过复制CharacterViewer.vue并将其命名为“ CharacterCreator.vue”,以替换代码来添加“角色创建者”组件:
<template>
<div class="character-creator">
<h1>Character Creator</h1>
</div>
</template>
<script>
export default {
name: 'CharacterCreator'
}
</script>
<style scoped>
</style>
Then reference our new component in App.vue:
然后在App.vue中引用我们的新组件:
<template>
<div id="app">
<CharacterViewer />
<CharacterCreator />
</div>
</template>
<script>
import CharacterViewer from './components/CharacterViewer.vue'
import CharacterCreator from './components/CharacterCreator.vue'
export default {
name: 'App',
components: {
CharacterViewer,
CharacterCreator
}
}
</script>
Cool. Now the website should show us:
凉。 现在,网站应向我们显示:
That's great, but let's say that we want to dynamically view each of the components independently of one another. We could use radial menus as selectors that will guide the logic of our app, but I'm partial to using buttons when creating a user interface.
很好,但是,我们要动态地相互独立地查看每个组件。 我们可以使用径向菜单作为选择器,以指导应用程序的逻辑,但是我偏爱在创建用户界面时使用按钮。
Let's add a couple:
让我们添加几个:
<template>
<div id="app">
<button v-on:click="toggle='character-viewer'">View all characters</button>
<button v-on:click="toggle='character-creator'">Create a character</button>
<CharacterViewer v-show="toggle==='character-viewer'" />
<CharacterCreator v-show="toggle==='character-creator'" />
</div>
</template>
<script>
import CharacterViewer from './components/CharacterViewer.vue'
import CharacterCreator from './components/CharacterCreator.vue'
export default {
name: 'App',
components: {
CharacterViewer,
CharacterCreator
},
data: function () {
return {
toggle: "character-viewer"
}
}
}
</script>
To understand the above code, let's work our way from the bottom of the script upwards.
为了理解上面的代码,让我们从脚本的底部开始。
We've added a "data" function to the export section of our app, which returns an object that can store data for us. This data can in turn help us manage the state of the app. In this code, we've created a "toggle" that's set to "character-viewer."
我们在应用程序的导出部分添加了“数据”函数,该函数返回一个可以为我们存储数据的对象。 这些数据反过来可以帮助我们管理应用程序的状态 。 在这段代码中,我们创建了一个“切换”,将其设置为“字符查看器”。
In the HTML template above the script, we've created two buttons: one to "View all characters" and the other to "Create a character." The attribute "v-on:click" within the <button> tags tells Vue that when clicked, Vue should change the value of "toggle" to "character-viewer" or "character-creator," depending on which button is being clicked.
在脚本上方HTML模板中,我们创建了两个按钮:一个用于“查看所有字符”,另一个用于“创建字符”。 <button>标记内的属性“ v-on:click”告诉Vue,当单击时,Vue应当根据所单击的按钮将“ toggle”的值更改为“ character-viewer”或“ character-creator”。 。
Just below the buttons, the "v-show" directives instruct Vue to only show the "CharacterViewer" component if "toggle" is equal to "character-viewer", or the "CharacterCreator" component if it's equal to "character-creator."
在按钮下方,“ v-show”指令指示Vue仅在“ toggle”等于“ character-viewer”时显示“ CharacterViewer”组件,或者在“ character-creator”相等时显示“ CharacterCreator”组件。 ”
Congrats, our app now renders content dynamically based on user input!
恭喜,我们的应用程序现在可以根据用户输入动态呈现内容!
Now, we can move to creating the basic structure for viewing and creating roleplaying game characters. In CharacterCreator.vue, update the code:
现在,我们可以开始创建用于查看和创建角色扮演游戏角色的基本结构。 在CharacterCreator.vue中,更新代码:
<template>
<div class="character-creator">
<h1>Character Creator</h1>
<label for="character-name">Character Name: </label>
<input type="text" id="character-name" v-model="name" placeholder="Enter a name" /> <br /><br />
<label for="professions-list">Character Profession: </label>
<select id="professions-list" v-model="profession">
<option value="Mage">Mage</option>
<option value="Thief">Thief</option>
<option value="Warrior">Warrior</option>
</select>
<p>{{name}}</p>
<p>{{profession}}</p>
</div>
</template>
<script>
export default {
name: 'CharacterCreator',
data: function () {
return {
name: "",
profession: ""
}
}
}
</script>
We've just created a text input where players can input a character name, and a simple dropdown list from which they can choose a profession.
我们刚刚创建了一个文本输入,玩家可以在其中输入角色名称,还可以从一个简单的下拉列表中选择职业。
The "v-model" attribute binds each of those inputs to the "name" and "profession" values in our data object within the script.
“ v-model”属性将这些输入中的每一个都绑定到脚本内我们数据对象中的“名称”和“专业”值。
We've also temporarily added a {{name}} and {{profession}} into the HTML template so that we can make sure that everything's working properly. Upon saving, the Vue CLI should automatically re-render the app to look like this when clicking on "Create a character":
我们还向HTML模板中临时添加了{{name}}和{{profession}},以确保一切正常。 保存后,Vue CLI在单击“创建角色”时应自动重新渲染应用程序,使其看起来像这样:
It's certainly not pretty, but it works! I'll leave the design up to your mad CSS skills.
当然不是很漂亮,但是可以! 我将把设计权留给您疯狂CSS技能。
后端 (The Back End)
Let's move to the back end. Open a new command line and navigate to the root directory (mevn-character-generator). Create a new directory for our server and navigate into it:
让我们移到后端。 打开一个新命令行,然后导航到根目录(mevn-character-generator)。 为我们的服务器创建一个新目录并导航到它:
mkdir server
cd server
Now initialize the directory:
现在初始化目录:
npm init
You can just keep hitting "enter" at the prompts if you don't care to change any of the specifics.
如果您不希望更改任何特定信息,则只需在提示处单击“输入”即可。
Then install our dependencies and save them to the project:
然后安装我们的依赖项并将其保存到项目中:
npm install --save express dotenv nodemon mongoose cors
Let's take a second to look at each of these items in turn. Express is going to serve as the main back end web framework, while dotenv allows us to declare certain environment variables that will help us with pathing and configuration. Nodemon automatically watches our server for changes and restarts it for us, and Mongoose serves as an ODM to map our data onto MongoDB. Finally, CORS allows us to make cross-origin requests between our client and server, a topic I've written about here.
让我们花点时间依次研究这些项目。 Express将用作主要的后端Web框架,而dotenv允许我们声明某些环境变量 ,这将有助于我们进行路径和配置。 Nodemon自动监视我们的服务器中的更改并为我们重新启动它, Mongoose充当ODM来将我们的数据映射到MongoDB。 最后, CORS允许我们在客户端和服务器之间发出跨域请求 ,这是我在此处写的主题。
That's a lot of dependencies! Back in our code editor, we need to create a few files and directories to scaffold a server with which to work. In our new /server directory, create four files called "server.js", ".env", "characters.js", and "character.js":
那有很多依赖! 回到我们的代码编辑器中,我们需要创建一些文件和目录来搭建一个服务器。 在我们新的/ server目录中,创建四个文件,分别为“ server.js”,“。env”,“ characters.js”和“ character.js”:
Replace the "test" script in our package.json with the "dev" one below:
将我们的package.json中的“测试”脚本替换为以下“开发”之一:
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"mongoose": "^5.9.3",
"nodemon": "^2.0.2"
},
"devDependencies": {},
"scripts": {
"dev": "nodemon server.js"
},
"author": "",
"license": "ISC"
}
Now, when we type "npm run dev" in the command line, it'll run Nodemon with server.js as the entry point for the back end of our app.
现在,当我们在命令行中键入“ npm run dev”时,它将使用server.js作为应用程序后端的入口点运行Nodemon。
We'll create our server by adding the following code to server.js:
我们将通过在server.js中添加以下代码来创建服务器:
require('dotenv').config();
const express = require('express');
const server = express();
const cors = require('cors');
server.use(express.json());
server.use(cors());
server.get("/", (req, res) => {
res.send("Hello World!");
})
server.listen(3000, () => console.log("Server started!"));
We're doing a lot here up front, but we'll thank ourselves later. First, we're importing any environmental variables that we'll need for running our development server, as well as Express and CORS. We're creating a server that runs on Express and is able to parse JSON and use CORS.
我们在这里要做很多事情,但是稍后我们会感谢自己。 首先,我们要导入运行开发服务器以及Express和CORS所需的所有环境变量。 我们正在创建一个在Express上运行的服务器,该服务器能够解析JSON并使用CORS。
Then, we're telling the server that when a user navigates to the root directory ("/") in a browser, they should be sent the message "Hello World!"
然后,我们告诉服务器,当用户在浏览器中导航到根目录(“ /”)时,应该向他们发送消息“ Hello World!”。
Finally, we tell the server to listen on port 3000, and log to the console that the "Server started!"
最后,我们告诉服务器侦听端口3000,并登录到控制台“服务器已启动!”。
Type the following in a separate command line from the one running our Vue app, making sure you're in the /server directory:
在与运行我们的Vue应用程序的命令行不同的命令行中键入以下内容,确保您位于/ server目录中:
npm run dev
Open a browser to http://localhost:3000. You should see:
打开浏览器到http:// localhost:3000。 您应该看到:
Neat!
整齐!
Now that the server's up, we need to get our database working. Open a third command line and type in the following:
现在服务器已启动,我们需要使数据库正常工作。 打开第三个命令行,然后键入以下内容:
mongod
This should get our database running, but will depend on how you installed and configured MongoDB before tackling this tutorial. In some cases, you'll need to work with the path of your database and of MongoDB itself to get it all square.
这应该让我们的数据库中运行,但将取决于你如何解决这个教程之前安装和配置MongoDB的。 在某些情况下,您需要使用数据库的路径以及MongoDB本身的路径才能使路径完全一致。
Once the "mongod" command is working, add the following line to your .env file:
一旦“ mongod”命令生效,请将以下行添加到您的.env文件中:
DATABASE_URL = mongodb://localhost/characters
We'll use the above in a second as we hook up our database. Add the following code to your server.js file, just under the line about requiring CORS:
在连接数据库时,我们将在一秒钟内使用以上内容。 将以下代码添加到您的server.js文件中,位于有关需要CORS的行的正下方:
const mongoose = require('mongoose');
mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true });
const db = mongoose.connection;
db.on('error', (error) => console.error(error));
db.once('open', () => console.log('Connected to database!'));
Here, we're importing Mongoose into our server, and connecting it to the DATABASE_URL that we declared in the .env file.
在这里,我们将Mongoose导入我们的服务器,并将其连接到在.env文件中声明的DATABASE_URL。
This connection is assigned to the "db" variable for easy reference, and if there's an error, we've asked the server to log it to the console. Otherwise, if everything's working correctly, the console should log that we're "Connected to database!"
此连接已分配给“ db”变量,以方便参考,如果有错误,我们已要求服务器将其记录到控制台。 否则,如果一切正常,控制台应记录我们已“连接到数据库!”。
Save all of your files, allowing Nodemon to restart the server with the CLI messages that the "Server started!" and that you're "Connected to database!"
保存所有文件,以使Nodemon可以使用“服务器已启动!”的CLI消息来重启服务器。 并且您已“连接到数据库!”
Now that everything's wired up on the back end, we'll need to add a Mongoose "schema," which is a model of what our data should look like. Add the below to character.js:
现在,所有内容都已连接到后端,我们需要添加Mongoose“模式”,这是我们的数据外观的模型。 将以下内容添加到character.js:
const mongoose = require('mongoose');
const characterSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
profession: {
type: String,
required: true
}
});
module.exports = mongoose.model('Character', characterSchema);
After importing Mongoose, we've added a new schema that maps the character name and profession that we've created in our front end client to the requisite fields in the back end database. Both are of type "String," and are required when posting to the database.
导入Mongoose之后,我们添加了一个新模式,该模式将在前端客户端中创建的角色名称和职业映射到后端数据库中的必填字段。 两者均为“字符串”类型,并且在发布到数据库时都是必需的。
We need to tell the server how to access the database and what to do once it's there, but it'll get messy if we try to add all of that code to server.js. Let's delete the code block that begins with "server.get..." and replace it with:
我们需要告诉服务器如何访问数据库以及访问数据库后该怎么做,但是如果我们尝试将所有代码添加到server.js中,就会变得混乱。 让我们删除以“ server.get ...”开头的代码块,并将其替换为:
const router = require('./characters');
server.use('/characters', router);
This snippet just says to the server, "when someone goes to the /characters HTTP endpoint, do whatever's in the characters.js file."
这个片段只是对服务器说:“当有人去/ characters HTTP端点时 ,请执行characters.js文件中的所有操作。”
Your entire server.js file should now look like the following:
您的整个server.js文件现在应如下所示:
require('dotenv').config();
const express = require('express');
const server = express();
const cors = require('cors');
const mongoose = require('mongoose');
mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true });
const db = mongoose.connection;
db.on('error', (error) => console.error(error));
db.once('open', () => console.log('Connected to database!'));
server.use(express.json());
server.use(cors());
const router = require('./characters');
server.use('/characters', router);
server.listen(3000, () => console.log("Server started!"));
Note: it's a best practice to keep your models and routes in "/models" and "/routes" folders, respectively, but we've simplified the paths for this tutorial.
注意:最佳做法是将模型和路径分别保存在“ / models”和“ / routes”文件夹中,但是我们简化了本教程的路径。
Let's get that characters.js file working. Start by entering the following:
让我们开始工作Characters.js文件。 首先输入以下内容:
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send("Hello World!")
});
module.exports = router;
If we navigate to http://localhost:3000/characters, we get the "Hello World!" message that we saw previously. Not too shabby – we've successfully migrated our code to a separate file to keep things a bit more tidy.
如果我们导航到http:// localhost:3000 / characters,则会得到“ Hello World!”。 我们之前看到的消息。 不太破旧-我们已经成功地将代码迁移到一个单独的文件中,以使事情更加整洁。
Adding a bit more to characters.js will help us fill out the remainder of our back end API, but let's pause for a moment to consider what we're attempting to do.
向character.js添加更多内容将有助于我们填充后端API的其余部分,但让我们暂停一下,以考虑我们正在尝试做的事情。
In this project, we want to be able to make GET and POST requests from the client to the server, which will in turn "Read" from and "Create" items in the database (representing the "R" and "C" in "CRUD"). We'll start with the GET method as we already have a structure for it:
在此项目中,我们希望能够从客户端向服务器发出GET和POST请求,这将依次从数据库中的“读取”项和“创建”项(分别代表“ R”和“ C” CRUD “)。 我们将从GET方法开始,因为我们已经有了它的结构:
const express = require('express');
const router = express.Router();
const Character = require('./Character');
router.get('/', async (req, res) => {
try {
const characters = await Character.find();
res.json(characters);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
module.exports = router;
We're creating an asynchronous function that, when it receives a request, attempts to find all Characters in our database that fit our Mongoose schema. It then sends them all back up the stack as a JSON response. If something goes awry, it instead sends a 500 error.
我们正在创建一个异步函数 ,当它接收到请求时,它会尝试在数据库中查找适合我们Mongoose模式的所有字符。 然后,将它们全部作为JSON响应发送回堆栈。 如果出现问题,则会发送500错误 。
Reloading the page that we have open on http://localhost:3000/characters will return an exciting "[]", but that's great! It just means that the GET request is returning an empty array because the database is empty. Good job!
重新加载我们在http:// localhost:3000 / characters上打开的页面将返回一个令人兴奋的“ []”,但这太好了! 这只是意味着GET请求将返回一个空数组,因为数据库为空。 做得好!
前端和后端接线 (Wiring the Front End and Back End)
Let's return to our client! In a command line at the mevn-character-generator/client directory, install Axios:
让我们回到我们的客户! 在mevn-character-generator / client目录的命令行中,安装Axios :
npm install --save axios
Axios allows us to make HTTP requests from within our client. If you're interested, you can read more about how it works with Vue here.
Axios允许我们从客户端内部发出HTTP请求。 如果您有兴趣,可以在此处阅读有关Vue如何工作的更多信息。
Back in our /client/src/components/CharacterViewer.vue file, we need to make GET requests to the server so that we can pull characters from the database, and we'll do so using Axios:
回到我们的/client/src/components/CharacterViewer.vue文件中,我们需要向服务器发出GET请求,以便我们可以从数据库中提取字符,而我们将使用Axios这样做:
<template>
<div class="character-viewer">
<h1>Character Viewer</h1>
<p>{{characters}}</p>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'CharacterViewer',
data: function () {
return {
characters: null
}
},
methods: {
getCharacters: function () {
axios
.get('http://localhost:3000/characters')
.then(response => (this.characters = response.data))
}
},
mounted: function () {
this.getCharacters();
}
}
</script>
<style scoped>
</style>
In the script section, we've created a data variable called "characters", which starts out as "null."
在脚本部分,我们创建了一个名为“ characters”的数据变量,该变量以“ null”开头。
In our "methods" object, which is where Vue stores functions that you can use throughout your component, we've created a "getCharacters()" function. "getCharacters()" will call Axios to GET the http://localhost:3000/characters endpoint and store the data of its response in the "characters" variable.
在Vue存储可在整个组件中使用的函数的“方法”对象中,我们创建了“ getCharacters()”函数。 “ getCharacters()”将调用Axios以获取http:// localhost:3000 / characters端点,并将其响应数据存储在“ characters”变量中。
When the component is mounted for the first time, it will run "getCharacters()" to GET all characters from the database and display them within the HTML in the template section above.
首次安装组件时,它将运行“ getCharacters()”以从数据库中获取所有字符,并在上述模板部分HTML中显示它们。
We still won't see anything exciting on our client page (still rendering at http://localhost:8080 or 8081) because we haven't added any characters to the database yet.
我们仍未在客户端页面上看到任何令人兴奋的内容(仍在http:// localhost:8080或8081上呈现),因为我们尚未向数据库中添加任何字符。
Pro tip! If you're nervous about this step and not sure if things are working correctly, you can use a third party app like Postman to make HTTP requests to an API without having to first wire up your client.
专家提示! 如果您对此步骤感到紧张,并且不确定是否可以正常工作,则可以使用Postman等第三方应用向API发出HTTP请求,而无需先连接客户端。
Let's jump back to our /server/characters.js router and add logic for a POST request:
让我们跳回到我们的/server/characters.js路由器,并为POST请求添加逻辑:
const express = require('express');
const router = express.Router();
const Character = require('./Character');
router.get('/', async (req, res) => {
try {
const characters = await Character.find();
res.json(characters);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
router.post('/', async (req, res) => {
const character = new Character({
name: req.body.name,
profession: req.body.profession
});
try {
const newCharacter = await character.save();
res.status(201).json(newCharacter);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
module.exports = router;
Below the GET request, we've added an asynchronous POST function that creates a "character," which is a new copy of the Character.js Mongoose schema. The request that comes to the server should include a "name" and "profession" in the body, which should be saved into the database as a "newCharacter" and returned as the JSON response with a 201 success.
在GET请求下方,我们添加了一个异步POST函数,该函数创建了一个“字符”,这是Character.js Mongoose模式的新副本。 到达服务器的请求的主体中应包含“名称”和“专业”,应将其作为“ newCharacter”保存到数据库中,并以JSON响应形式返回( 成功为201) 。
If there's an error, the server should send it up the chain with a status of 400.
如果出现错误,服务器应将其发送到状态为400的链上。
Crazily enough, this code is all we need to wrap up the back end of our app. If we head to our /client/src/components/CharacterCreator.vue file, we can tie everything together:
足够疯狂的是,这些代码是我们包装应用程序后端的全部。 如果我们转到/client/src/components/CharacterCreator.vue文件,则可以将所有内容捆绑在一起:
<template>
<div class="character-creator">
<h1>Character Creator</h1>
<label for="character-name">Character Name: </label>
<input type="text" id="character-name" v-model="name" placeholder="Enter a name" /> <br /><br />
<label for="professions-list">Character Profession: </label>
<select id="professions-list" v-model="profession">
<option value="Mage">Mage</option>
<option value="Thief">Thief</option>
<option value="Warrior">Warrior</option>
</select><br /><br />
<button v-on:click="postCharacter">Create Character</button>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'CharacterCreator',
data: function () {
return {
name: null,
profession: null
}
},
methods: {
postCharacter: function () {
axios
.post('http://localhost:3000/characters', {
name: this.name,
profession: this.profession
});
}
}
}
</script>
We've added a "postCharacter()" function to the CharacterCreator.vue component, which will send a POST request to http://localhost:3000/characters endpoint with a "name" and "profession" in the body.
我们已经在CharacterCreator.vue组件中添加了一个“ postCharacter()”函数,该函数将向Http:// localhost:3000 / characters端点发送一个POST请求,该请求的正文中带有“ name”和“ profession”。
The "name" and "profession" are drawn from the variables within our data object, which are themselves bound to the inputs that we created earlier by the "v-model" attribute.
“名称”和“专业”是从数据对象内的变量中提取的,这些变量本身已绑定到我们之前通过“ v-model”属性创建的输入。
We've added a "Create Character" button that calls the "postCharacter()" function when clicked. When we make a POST request using the character creator, we can now do this:
我们添加了一个“创建角色”按钮,该按钮在单击时会调用“ postCharacter()”函数。 当我们使用角色创建者发出POST请求时,我们现在可以执行以下操作:
And our GET request looks like:
我们的GET请求如下所示:
IT'S WORKING. But we need to clean up our GET request so that it's more readable, especially when new users are added. Here's what we'll add to the <template> portion of CharacterViewer.vue:
它正在工作。 但是我们需要清理GET请求,以使其更具可读性,尤其是在添加新用户时。 这是我们将添加到CharacterViewer.vue的<template>部分的内容:
<template>
<div class="character-viewer">
<h1>Character Viewer</h1>
<p v-for="(character, index) in characters" v-bind:key="index">{{character.name}} is a {{character.profession}}!</p>
<button v-on:click="getCharacters">Refresh Characters</button>
</div>
</template>
Here, we're using "v-for" to ask Vue to iterate over each of the characters in the response data (currently stored in the "characters" variable) and display their names and professions.
在这里,我们使用“ v-for”来要求Vue遍历响应数据中的每个字符(当前存储在“字符”变量中)并显示其名称和职业。
The Vue CLI will get irritated if you don't provide a unique key for each of the iterated items, so we use "v-bind" to create a key based on the item's index.
如果您没有为每个迭代项提供唯一的键,则Vue CLI会感到恼火,因此我们使用“ v-bind”根据项的索引创建键。
We've also added a "Refresh Characters" button that will call the "getCharacters()" function so we can see new characters as they're added without having to refresh the page.
我们还添加了一个“刷新字符”按钮,该按钮将调用“ getCharacters()”函数,这样我们就可以在添加新字符时看到它们,而不必刷新页面。
The Character Viewer looks a lot cleaner:
角色查看器看起来更加干净:
And with that, our app is fully functional! Great job!
如此一来,我们的应用程序即可正常运行! 很好!
...
...
...
...
But what if we want to eliminate that "Refresh Characters" button and just have all characters load whenever we navigate to the Character Viewer section of the app?
但是,如果我们想消除该“刷新字符”按钮,并且只要我们导航到应用程序的“字符查看器”部分,仅加载所有字符,该怎么办?
First, we'll need to make these changes to App.vue:
首先,我们需要对App.vue进行以下更改:
<template>
<div id="app">
<button v-on:click="toggle='character-viewer'; getCharacters()">View all characters</button>
<button v-on:click="toggle='character-creator'">Create a character</button>
<CharacterViewer v-show="toggle==='character-viewer'" :characters="characters"/>
<CharacterCreator v-show="toggle==='character-creator'" />
</div>
</template>
<script>
import CharacterViewer from './components/CharacterViewer.vue'
import CharacterCreator from './components/CharacterCreator.vue'
import axios from "axios"
export default {
name: 'App',
components: {
CharacterViewer,
CharacterCreator
},
data: function () {
return {
toggle: "character-viewer",
characters: null
}
},
methods: {
getCharacters: function () {
axios
.get('http://localhost:3000/characters')
.then(response => (this.characters = response.data))
}
},
mounted: function () {
this.getCharacters();
}
}
</script>
We've migrated the "getCharacters()" functionality to App.vue and are now calling it when the app is mounted, as well as whenever we click on the "View all characters" button. We're also passing the "characters" variable - which is storing our response data from the server API - as props to the "CharacterViewer" component in the <template> section.
我们已经将“ getCharacters()”功能迁移到App.vue,现在在安装应用程序以及单击“查看所有字符”按钮时调用它。 我们还将传递“ characters”变量-存储来自服务器API的响应数据-作为对<template>部分中“ CharacterViewer”组件的支持。
All that's left is to clean up CharacterViewer.vue and indicate that it should expect an Array called "characters" as props:
剩下的就是清理CharacterViewer.vue并指出它应该以一个名为“ characters”的数组作为道具:
<template>
<div class="character-viewer">
<h1>Character Viewer</h1>
<p v-for="(character, index) in characters" v-bind:key="index">{{character.name}} is a {{character.profession}}!</p>
</div>
</template>
<script>
export default {
name: 'CharacterViewer',
props: {
characters: Array
}
}
</script>
WE'VE DONE IT.
我们已经完成了。
We've created a fully functional roleplaying game character generator. Its Vue client responds dynamically to user input, and can make GET and POST requests to a Node/Express server API, which in turn reads from and writes to a MongoDB database.
我们创建了一个功能齐全的角色扮演游戏角色生成器。 它的Vue客户端可以动态响应用户输入,并且可以向Node / Express服务器API发出GET和POST请求,然后从该服务器API读取和写入MongoDB数据库。
Well done. You can use this project as a template for your own MEVN full stack apps, or work with the HTML and CSS to make it more feature-rich and user friendly.
做得好。 您可以将该项目用作自己的MEVN全栈应用程序的模板,或者与HTML和CSS一起使用,以使其功能更丰富且更友好。
A fun next step would be to research RESTful APIs in more depth and add PATCH and DELETE requests so that you can update or delete characters as necessary. A helpful starting point would be the Express documentation, or Bennett Dungan's article on building a REST API.
有趣的下一步是更深入地研究RESTful API ,并添加PATCH和DELETE请求,以便您可以根据需要更新或删除字符。 Express文档或Bennett Dungan关于构建REST API的文章是一个有用的起点。
You can also learn how to deploy this kind of app to Heroku here.
您还可以在此处了解如何将此类应用程序部署到Heroku。
Happy coding!
编码愉快!
If you enjoyed this article, please consider checking out my games and books, subscribing to my YouTube channel, or joining the Entromancy Discord.
如果您喜欢这篇文章,请考虑查看我的游戏和书籍 , 订阅我的YouTube频道或加入Entromancy Discord 。
M. S. Farzan, Ph.D. has written and worked for high-profile video game companies and editorial websites such as Electronic Arts, Perfect World Entertainment, Modus Games, and MMORPG.com, and has served as the Community Manager for games like Dungeons & Dragons Neverwinter and Mass Effect: Andromeda. He is the Creative Director and Lead Game Designer of Entromancy: A Cyberpunk Fantasy RPG and author of The Nightpath Trilogy. Find M. S. Farzan on Twitter @sominator.
法赞(MS Farzan)博士 他曾为知名的视频游戏公司和编辑网站(例如,Electronic Arts,Perfect World Entertainment,Modus Games和MMORPG.com)撰写和工作,并曾担任《龙与地下城:龙骨无双》和《 质量效应:仙女座》等游戏的社区经理。 。 他是《 Entronancy:Cyberpunk Fantasy RPG》的创意总监和首席游戏设计师,并且是《 The Nightpath Trilogy》的作者。 在Twitter @sominator上找到MS Farzan 。
翻译自: https://www.freecodecamp.org/news/build-a-full-stack-mevn-app/