构建node.js基础镜像
by Aurélien Giraud
通过AurélienGiraud
在Android上构建Node.js应用程序 (Building a Node.js application on Android)
第2部分:Express和NeDB (Part 2: Express and NeDB)
In Part 1 we saw how to use Termux, a Terminal emulator and Linux environment for Android. We also edited files with Vim and saw how to run Node. We are now going to build a small node application with the Express framework and use NeDB for the database.
在第1部分中,我们了解了如何使用Termux,这是用于Android的终端仿真器和Linux环境。 我们还使用Vim编辑了文件,并了解了如何运行Node。 现在,我们将使用Express框架构建一个小型节点应用程序,并将NeDB用于数据库。
这个故事,以及谁可以从中受益 (The story, and who could benefit from it)
When I found out I could build a full Node.js application with a Mongo-like database on my Android tablet, I got really excited. So I decided to give it a try, and share about my experience.
当我发现可以在Android平板电脑上使用类似于Mongo的数据库构建完整的Node.js应用程序时,我感到非常兴奋。 因此,我决定尝试一下,并分享自己的经验。
It turns out that once Termux is running on Android and Node is installed, the fact that we are on Android instead of Linux doesn’t make much of a difference. In fact, all the Termux specific setup was done in Part 1 and you are welcome to code along on your preferred device/computer/cloud IDE…
事实证明,一旦Termux在Android上运行并安装了Node,那么我们使用Android而不是Linux的事实并没有多大区别。 实际上,所有Termux特定的设置都是在第1部分中完成的,欢迎您在首选的设备/计算机/云IDE上进行编码……
This also means that, apart from the fact that we substitute Mongo for NeDB, this article is like the usual introduction to building a RESTful API and is mainly for people who are rather new to Node, Express and Mongo/NeDB.
这也意味着,除了我们用Mongo代替NeDB之外,本文就像构建RESTful API的常规介绍一样,主要针对Node,Express和Mongo / NeDB的新手。
我们要做什么 (What we are going to do)
In order to demonstrate how to get started with the Express web framework and an NeDB database, let’s look at a basic goal tracker that I’ve been building for myself. At the current stage, it looks as shown in the picture above.
为了演示如何开始使用Express Web框架和NeDB数据库,让我们看一下我为自己构建的基本目标跟踪器。 在当前阶段,它看起来如上图所示。
Users can:
用户可以:
- submit a new goal 提交新目标
- delete a goal 删除目标
- record a success for a goal 为目标记录成功
- record a failure for a goal 记录目标失败
Well, actually we’re only going to implement the first two features in this post, and in case you’re curious about the two remaining ones, I will provide at the end a link to the code of the full implementation.
好吧,实际上,我们只打算实现本文中的前两个功能,并且如果您对其余两个功能感到好奇,我将在最后提供一个指向完整实现代码的链接。
So without the need for recording successes and failures, our code will look a bit simpler:
因此,无需记录成功和失败的情况,我们的代码将看起来更简单:
Here are the steps we are going to go through:
这是我们要经历的步骤:
Set up the server with Express.
使用Express设置服务器。
Describe a few user stories.
描述一些用户故事。
Set up NeDB.
设置NeDB 。
Build a RESTful API.
构建一个RESTful API 。
先决条件 (Pre-requisites)
We are going to start where we left off in Part 1. Thus, the only requirement is that node be installed.
我们将从第1部分中停止的地方开始。因此,唯一的要求就是安装节点。
1.使用Express设置服务器 (1. Setting up the server with Express)
Express is a web framework for Node that helps build Node applications. If you have trouble figuring out what Express brings to Node, I recommend you check out Evan Hahn’s article Understanding Express.
Express是Node的Web框架,可帮助构建Node应用程序。 如果您无法确定Express给Node带来了什么,我建议您查看Evan Hahn的文章理解Express 。
Let’s start a new project:
让我们开始一个新项目:
$ mkdir goals-tracker && cd goals-tracker$ npm init$ touch server.js
and install Express:
并安装Express:
npm install express --save
We are going to use Express to define the routes, that is to define the application end points (URIs) and set up how the application responds to client requests.
我们将使用Express定义路由,即定义应用程序端点(URI)并设置应用程序如何响应客户端请求。
Open server.js and copy-paste/write:
打开server.js并复制粘贴/写入:
With that in place, you can start the app:
有了它,您可以启动应用程序:
$ node server.js
This should print to the console the number of the port on which the server is listening. If you visit http://localhost:8080 in the browser (assuming that 8080 is the number that got printed to the console) you should see on the page: Our first route is working. :)
这应该在控制台上显示服务器正在侦听的端口号。 如果您在浏览器中访问http:// localhost:8080 (假设8080是打印在控制台上的数字),则您应该在页面上看到: 我们的第一个途径正在起作用。 :)
一些解释 (Some explanations)
The ‘/’ in app.get( … ) defines the route where we want to attach some behavior from the server. Using ‘/’ refers to the base URI, in our case: http://localhost:8080. Note that we would get the same result in the browser window if we used app.get(‘/goals’, …) instead and visited http://localhost:8080/goals.
app.get(…)中的'/'定义了我们要从服务器附加某些行为的路由。 在本例中,使用'/'表示基本URI: http:// localhost:8080 。 请注意,如果我们改用app.get('/ goals',…)并访问http:// localhost:8080 / goals ,则会在浏览器窗口中获得相同的结果。
The second argument in app.get( … ) is a callback function that enables us to define what the server should do when the route given as the first argument is visited. In this function:
app.get(…)中的第二个参数是一个回调函数,使我们能够定义在访问作为第一个参数给出的路由时服务器应执行的操作。 在此功能中:
req stands for the request: this is the information that the server receives from a client (for example this might come from someone using the front-end part of the website/app).
req代表请求:这是服务器从客户端收到的信息(例如,该信息可能来自使用网站/应用程序前端部分的人)。
res stands for response: this is the information that the server sends back to the user. This can be a webpage or some other data like an image, some JSON or some XML.
res代表响应:这是服务器发回给用户的信息。 这可以是网页或其他一些数据,例如图像,一些JSON或一些XML。
Nodemon (Nodemon)
In the next parts of this tutorial we are going to make multiple changes to the file server.js. In order to avoid restarting the server manually each time to see the result, we can install nodemon.
在本教程的下一部分中,我们将对文件server.js进行多次更改。 为了避免每次手动重新启动服务器来查看结果,我们可以安装nodemon 。
Nodemon is a utility that will monitor for changes in your code and automatically restart the server. We are going to install it as a development only dependency using the tag save-dev:
Nodemon是一个实用程序,它将监视代码中的更改并自动重新启动服务器。 我们将使用标签save-dev将其安装为仅限开发的依赖项:
npm install nodemon --save-dev
Now you can restart the server with the nodemon command instead of node:
现在,您可以使用nodemon命令而不是node重新启动服务器:
nodemon server.js
2.用户故事 (2. The user stories)
Before we move on to the part about NeDB, let’s pause for a moment to think about the business logic. In order to see what we need to implement, we start by defining a few user stories.
在继续介绍有关NeDB的部分之前,让我们暂停一下,思考一下业务逻辑。 为了了解我们需要实现的内容,我们首先定义一些用户故事。
A user story is a very high-level definition of a requirement. User stories are useful for discussing the product in non-technical terms with a client, for estimating how much time and effort the implementation of a feature will take, for guiding the overall development of an application, and for doing Test Driven Development.
用户故事是需求的非常高级的定义。 用户故事对于与客户以非技术性的角度讨论产品,估计实现功能所需的时间和精力,指导应用程序的整体开发以及进行测试驱动开发很有用。
Here are the 4 user stories we’re going to use:
这是我们将要使用的4个用户故事:
- As a user, I can save a new goal with its date of creation. 作为用户,我可以保存一个新目标及其创建日期。
- As a user, I can access all the goals that have been saved. 作为用户,我可以访问所有已保存的目标。
- As a user, I can access the whole information about a goal. 作为用户,我可以访问有关目标的全部信息。
- As a user, I can delete a goal. 作为用户,我可以删除目标。
In our case, the stories map one-to-one to the 4 CRUD operations that we are going to talk about in Part 4.
在我们的案例中,故事一对一映射到我们将在第4部分中讨论的4个CRUD操作。
3.使用NeDB (3. Using NeDB)
The fact that NeDB is easy to install, well documented and uses the MongoDB’s API makes it perfect for getting started with developing Node.js applications on Android. There even is a tool to help you switch to MongoDB later on if need be (I haven’t tried it yet, though).
NeDB易于安装,文档齐全并且使用MongoDB的API,这一事实使其非常适合开始在Android上开发Node.js应用程序。 甚至还有一个工具可以帮助您在以后需要时切换到MongoDB(尽管我还没有尝试过)。
So let us add NeDB to the project:
因此,让我们将NeDB添加到项目中:
$ npm install nedb --save
and add to server.js a few lines to setup the database and make sure we can save to it:
并在server.js中添加几行来设置数据库,并确保我们可以保存到数据库:
A Datastore refers to what would be called a collection in Mongo. We could create multiple datastores if we needed several collections. As demonstrated in NeDB’s documentation, each collection would be saved in a separate file. Here we have chosen to store the goals collection in a file named goals.db.
数据存储区指的是Mongo中的集合。 如果需要几个集合,我们可以创建多个数据存储。 如NeDB文档所示,每个集合将保存在单独的文件中。 在这里,我们选择将目标集合存储在名为Goals.db的文件中。
检查是否有效 (Checking if it worked)
If the server was started earlier with nodemon, it should have updated after the changes in server.js got saved. This means that db.insert(…) should have run and the goal should have been logged to the console:
如果服务器是使用nodemon较早启动的 ,则应该在保存server.js中的更改后进行更新。 这意味着db.insert(…)应该已经运行,并且目标应该已经记录到控制台:
$ nodemon server.js[nodemon] 1.9.1[nodemon] to restart at any time, enter `rs`[nodemon] watching: *.*[nodemon] starting `node server.js`Listening on port 8080[nodemon] restarting due to changes...[nodemon] starting `node server.js`Listening on port 8080{ description: 'Do 10 minutes meditation every day', successes: [], failures: [], _id: 'ScfixKjsOqz9xBo5', createdAt: Fri Mar 18 2016 22:10:58 GMT+0000 (UTC), updatedAt: Fri Mar 18 2016 22:10:58 GMT+0000 (UTC) }
A new file called goals.db should also have been created.
还应该创建一个名为Goal.db的新文件。
$ ls goals.db node_modules/ package.json server.js
And it should contain the goal that just got saved.
它应该包含刚刚保存的目标。
$ less goals.db{"description":"Do 10 minutes meditation every day","_id":"ScfixKjsOqz9xBo5","createdAt":{"$$date":1458339058282},"updatedAt":{"$$date":1458339058282}}
Note that the fields _id, createdAt and updatedAt have been filled in automatically by NeDB because we set up the Datastore with the option timestampData set to true.
请注意,fields _id,createdAt和updatedAt已经自动NeDB填写,因为我们建立了数据存储与timestampData设置选项设置为true。
4.构建RESTful API (4. Building a RESTful API)
Next, let’s build a RESTful API for the application. In a nutshell, this means that we want to use HTTP verbs and URIs in order to allow the client to perform CRUD operations (Create, Read, Update and Delete). These operations are also usually going to send back data to the client.
接下来,让我们为应用程序构建一个RESTful API。 简而言之,这意味着我们要使用HTTP动词和URI,以便允许客户端执行CRUD操作(创建,读取,更新和删除)。 这些操作通常还会将数据发送回客户端。
In CRUD terms we can :
用CRUD术语,我们可以:
Create data with POST,
使用POST 创建数据,
Read data with GET,
使用GET 读取数据,
Update data with PUT or PATCH,
使用PUT或PATCH 更新数据,
Delete data with DELETE.
使用DELETE 删除数据。
The HTTP verbs that we are going to use in this post are POST, GET and DELETE.
我们将在本文中使用的HTTP动词是POST,GET和DELETE。
我们的API (Our API)
Here is a table showing the routes that we are going to set up, how they will be accessed (i.e. with which HTTP Verb) and what each one makes possible:
下表显示了我们将要设置的路由,如何访问它们(即使用哪个HTTP Verb)以及每个路由可能实现的功能:
If you want to learn more about RESTful APIs, you could check out Designing a RESTful Web API by Mathias Hansen or Using HTTP Methods for RESTful Services.
如果您想了解有关RESTful API的更多信息,可以查看Mathias Hansen的Designing RESTful Web API或使用RESTful Services使用HTTP方法 。
测试API (Testing the API)
We are going to test the API manually in the terminal using curl. If you are not on Android and would rather like to use a GUI to test the API, you could use POSTMAN.
我们将使用curl在终端中手动测试API。 如果您不在Android上,并且想使用GUI来测试API,则可以使用POSTMAN 。
Let see a first example of how to use curl. Make sure the server is running, open another terminal (in Termux swipe to the right from the left border and click on new session) and type:
让我们看一下如何使用curl的第一个例子。 确保服务器正在运行,打开另一个终端(在Termux中,从左边框向右滑动并单击new session ),然后键入:
$ curl -X GET "http://localhost:8080"
This should print to the console what we got in the browser window earlier on, that is: Our first route is working. :)
这应该将我们先前在浏览器窗口中看到的内容打印到控制台,即: 我们的第一个路线正在工作。 :)
We will now add code to server.js bits by bits. In case you would rather like to see the ‘big’ picture first you can head over to the final server.js file.
现在,我们将代码逐位添加到server.js中 。 如果您想首先看到“大”的图片,可以转到最终的server.js文件 。
人体解析器 (Body-parser)
To handle the requests that the server receives, we are going to install body-parser. It processes the incoming requests and makes it easier for us to access the relevant parts.
为了处理服务器收到的请求,我们将安装body-parser 。 它处理传入的请求,使我们更容易访问相关部分。
npm install body-parser --save
Add the body-parser setup code to the top of server.js (e.g. right after the setup of the port number):
将正文分析器设置代码添加到server.js的顶部(例如, 紧随端口号设置之后):
实现所有目标 (Getting all the goals)
If the server receives a GET request at /goals, the callback will be executed and the database will be queried with db.find({}). Here the object passed to find() is empty. This means that no constraint is set to what we are looking for and all objects in the database should be returned.
如果服务器在/ goals处收到GET请求,则将执行回调,并使用db.find({})查询数据库。 这里传递给find()的对象为空。 这意味着没有为我们要查找的内容设置任何约束,并且应该返回数据库中的所有对象。
Notice also that no callback has been specified to find(). Thus a Cursor object is returned, which can first be modified with sort, skip or limit before we use exec(callback) to finish the query. Here we are using sort to return the goals with the most recently updated ones at the top (i.e. the ones with the ‘greater’ date of last update).
还要注意,没有为find()指定回调。 这样就返回了一个Cursor对象,在使用exec(callback)完成查询之前,可以先使用sort , skip或limit对其进行修改。 在这里,我们使用排序来返回目标,这些目标的顶部是最近更新的目标(即,上次更新日期为“较大”的目标)。
If everything went well, the result of the query (in our case an array of goals) is sent back to the client formatted as JSON. In case something went wrong and an error is produced, the error message is sent back to the client instead.
如果一切顺利,查询结果(在本例中为目标数组)将以JSON格式发送回客户端。 如果出现问题并产生错误,则会将错误消息发送回客户端。
Let us test if it works:
让我们测试一下是否可行:
$ curl -X GET "http://localhost:8080/goals/"
This should print to the console an array containing the goal we saved to the database earlier on.
这应该在控制台上打印一个数组,其中包含我们之前保存到数据库中的目标。
建立目标 (Creating a goal)
req.body contains key-value pairs of data that was submitted in the request body. By default, it is undefined and it gets populated by the body-parser middleware. In our case the request should contain a key-value pair whose key is named ‘description’ and whose value is thus retrieved by using req.body.description.
req.body包含在请求正文中提交的键-值数据对。 默认情况下,它是未定义的,并且由主体解析器中间件填充。 在我们的示例中,请求应包含一个键值对,其键名为“ description” ,因此可以使用req.body.description检索其值。
So first, the goal we want to insert into the database is built from the request using req.body.description. Then it can be inserted into the database and if there was no error the response is sent back to the server as JSON.
因此,首先,我们要插入数据库的目标是使用req.body.description从请求中构建的 。 然后可以将其插入数据库,如果没有错误,则将响应作为JSON发送回服务器。
Now let us try to POST a new goal using curl:
现在让我们尝试使用curl来发布新目标:
$ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "description=Read about functional programming every day" "http://localhost:8080/goals/"
This should print the JSON representation of the goal that has been sent back to the client.
这应该打印已发送回客户端的目标的JSON表示形式。
We post the data as x-www-form-urlencoded. This sends the data as query strings that are parsed by the body-parser.
我们将数据发布为x-www-form-urlencoded 。 这会将数据作为由body-parser解析的查询字符串发送。
使用其ID获取目标 (Getting a goal using its id)
req.params is an object containing properties mapped to the route “parameters”. Here it enables us to access the value of the goal’s id, which is supposed to come after /goals/ in the URL in the request. For this to work, we have to use a colon in the URI in front of the property that we want to access with req.params.
req.params是一个对象,其中包含映射到路由“参数”的属性。 在这里,我们可以访问目标ID的值,该值应该位于请求中URL中的/ goals /之后。 为此,我们必须在要使用req.params访问的属性前面的URI中使用冒号。
Apart from the fact that we are using findOne(…) instead of find(…), nothing new here. So let’s test it. For this, you might check what got printed to the console after we saved a new goal using POST and use its “_id” value. Here is my command with the id I got:
除了我们使用的是findOne(...)而不是find(...)以外 ,这里没有什么新内容。 因此,让我们对其进行测试。 为此,您可以检查在使用POST保存新目标并使用其“ _id”值后打印到控制台的内容。 这是我的命令,其ID为:
$ curl -X GET "http://localhost:8080/goals/JJtcFVQnoAxW7KXc"
This should print to the console the goal with the given id.
这应该将具有给定ID的目标打印到控制台。
使用其ID删除目标 (Deleting a goal using its id)
We use remove(…) to delete a goal from the database. If the deletion is successful, the response is sent with the HTTP status code 200 (200 means that the deletion was successful).
我们使用remove(…)从数据库中删除目标。 如果删除成功,则使用HTTP状态代码200发送响应( 200表示删除成功 )。
结语 (Wrapping it up)
We have set up a server with Express and NeDB, and built a REST API. What we are still missing is a front-end and a bit of wiring.
我们已经使用Express和NeDB设置了服务器,并构建了REST API。 我们仍然缺少的是前端和一些布线。
This next step could take us down many different roads: Would we opt for a template engine and if yes which one? Bootstrap or a similar framework? Angular, React, Aurelia? The list just goes on and on.
下一步可能会带我们走许多不同的道路:我们会选择模板引擎吗? 引导程序还是类似的框架? Angular,React,Aurelia? 这份清单不胜枚举。
In case you would like to have a look at a minimal implementation of a front-end — and maybe play around with it in your browser — you can see the code of a possible solution I have been implementing using Handlebars, Material Design Lite and the fetch API by visiting its repo on GitHub or by cloning it:
如果您希望了解前端的最小实现,并且可以在浏览器中试用它,则可以看到我一直在使用Handlebars , Material Design Lite和通过访问GitHub上的仓库或克隆它来获取API :
$ git clone --branch rest-and-view https://github.com/aurerua/goals-tracker.git --depth 1
更进一步 (Going further)
The back-end that we have built still raises the question: how should the code be split into different files and folders for better modularity and maintainability?
我们构建的后端仍然引发一个问题:如何将代码拆分为不同的文件和文件夹,以实现更好的模块化和可维护性?
In case you’re curious, I have also been writing another version of the app that uses a Model-View-Controller folder structure. Feel free to have a look:
万一您感到好奇,我还一直在编写使用Model-View-Controller文件夹结构的应用程序的另一个版本 。 随意看看:
$ git clone https://github.com/aurerua/goals-tracker-mvc.git
And if you have any question or feedback, do not hesitate to contact me!
如果您有任何疑问或反馈,请随时与我联系!
构建node.js基础镜像