如何使用 Nodejs 制作待办事项列表?
待办事项列表是许多编程学生制作的初学者应用程序。它是您一天内需要完成的任务列表。您可以向其中添加任务,甚至在完成后将其删除。通常可以看到,许多教程仅将待办事项列表应用程序留在前端部分。读者永远不会知道如何将它与后端集成,以便使用适当的数据库并且在刷新/重新启动时不会丢失数据。本文旨在教你如何制作一个完整的待办事项列表项目。
本文面向已获得 Web 开发基础知识但尚未构建任何项目的读者。许多学生知道语法,但无法应用。仅了解某些技术(例如 Nodejs)是不够的,还需要能够将其与其他技术一起使用并使用编程逻辑构建应用程序。这篇文章是为了帮助这些人通过构建项目来提高他们的知识。
我们的应用程序的特点:
- 动态前端,通过 EJS,它是一个 NPM 包。
- 使用 Nodejs 构建的后端。
- 使用的数据库:MongoDB Atlas。
应用程序如何工作:
- 呈现主页,用户可以在其中查看他的任务并插入新任务。
- 当我们点击添加按钮时,一个新的请求被发送到服务器端。
- 一旦任务完成,当我们检查它时,就会发送一个删除该项目的新请求。
注意:本文将主要关注后端,因此不会给出前端部分的详细解释,即HTML和CSS,但会提供代码。
让我们从逐步实施开始。
第 1 步:在 VS Code 中打开一个空文件夹。在此文件夹中创建一个名为 index .js的文件。
步骤 2:在终端中编写以下命令,以初始化此文件夹以制作 Nodejs 项目。
npm init
第 3 步:在此之后,编写以下命令来安装我们将在应用程序中使用的一些包:
npm install –save express ejs body-parser mongodb mongoose
说明:上面的命令安装了我们应用程序中需要的包。
- ejs用于在前端呈现内容。
- express 是一个用于帮助代码冗余的 Nodejs 框架。
- body-parser用于从传入请求中读取数据。
- mongodb能够使用数据库,而mongoose是其代码冗余和易于连接的框架。
现在在我们的 app.js 旁边创建两个文件夹,将它们命名为 public(对于我们想要显示给用户的文件)和 views(对于 EJS 文件)。
项目结构:它将如下所示。
第 4 步:现在打开您的 index.js 文件。在这个文件中,我们将对我们的服务器进行编码。我们在哪里处理来自浏览器的请求,管理其中的数据并做出相应的响应。我们将处理两种类型的请求,这是最常用的,get 和 post。获取请求用于从服务器读取并将请求以书面形式发送到服务器。我们还将定义访问此应用程序服务器的服务器在我们机器的哪个端口上。这是我们所有应用程序逻辑所在的文件。我们甚至在这个文件中连接到一个云数据库。
index.js
// To use the packages installed, we import
// them using require and save them in
// a constant
const express = require("express");
const bodyParser = require("body-parser");
const ejs = require("ejs");
const mongoose = require("mongoose");
// Initializing a constant to use express
// methods and create middlewares.
const app = express();
// Telling Node.js to use body-parser for
// reading data coming from our
// incoming requests in URL
app.use(bodyParser.urlencoded({ extended: true }));
// Telling Nodejs that all our static
// files(here: CSS files) are
// stored in public folder
app.use(express.static("public"));
// Telling Nodejs that we will write our
// frontend in ejs files. Thus viewing
// engine has to be set to use ejs
app.set("view engine", "ejs");
index.js
// Make sure you did not use any special
// characters(e.g. @) in your user-name
// or password
mongoose.connect(
"mongodb+srv://:@cluster0.38u1b.mongodb.net/todoDB");
// Defining the schema or structure of
// a single item in mongodb
const taskSchema = {
name: {
type: String,
required: true
}
};
// Using the following code, node.js
// creates a collection named
// 'tasks' using the taskSchema
const Task = mongoose.model("Task", taskSchema);
index.js
app.get("/", function (req, res) {
// Getting today's date to display
// on top of our to-do
let today = new Date();
let options = {
weekday: "long",
day: "numeric",
month: "long"
};
// If we do not use the first argument
// in below line, which is "en-US" we get
// date in form of numbers only, separated
// with a /, thus the day won't be known
let day = today.toLocaleDateString("en-US", options);
// Find is a function given by mongoose, which
// is applied to a collection, it returns
// all the documents found in it.
Task.find({}, function (err, foundTasks) {
if (err) {
console.log(err)
}
else {
// Render the file names index.ejs and
// send the object to with the following
// data, sent as second parameter, in
// which we send date
// and tasks found in database.
res.render("index", { today: day, tasks: foundTasks });
}
})
});
index.ejs
To-do List
<%= today %>
<% if (tasks.length===0) {%>
No Task Added Yet
<% } else { %>
<% for (var i=0; i< tasks.length; i++){ %>
<% } %>
<% } %>
styles.css
*{
font-family: cursive;
box-sizing: border-box;
}
h1 {
padding: 10px;
}
.box {
max-width: 450px;
margin: 20px auto;
background: white;
border-radius: 5px;
box-shadow: 7px 7px 15px 5px rgba(0, 0, 0, 0.3);
}
#heading {
background-color: #353434;
text-align: center;
}
.item {
min-height: 70px;
display: flex;
align-items: center;
border-bottom: 1px solid #F1F1F1;
}
.item:last-child {
border-bottom: 0;
}
input:checked+p {
text-decoration: line-through;
text-decoration-color: #353434;
}
input[type="checkbox"] {
appearance: none;
margin: 20px;
height: 25px;
width: 25px;
border: 2px solid black;
border-radius: 3px;
}
input[type="checkbox"]:hover,
input[type="checkbox"]:focus{
transform: scale(1.2);
background-color: #353434;
color: white;
cursor: pointer;
}
input[type="checkbox"]:checked{
clip-path: polygon(14% 44%, 0 65%,
50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
p {
text-align: left;
margin: 0;
padding: 20px;
font-size: 1.8rem;
font-weight: bold;
color: #353434;
text-shadow: 2px 2px gray;
}
form {
text-align: center;
padding: 10px;
}
button {
width: 50px;
height: 50px;
border-radius: 50%;
font-size: 1.5rem;
background-color: #494848;
border-top: 4px solid white;
border-left: 4px solid white;
border-bottom: 4px solid black;
border-right: 4px solid black;
color: white;
}
button:hover{
cursor: pointer;
color: #494848;
background-color: white;
border: 3px solid #494848;
box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.459);
}
input[type="text"] {
margin: 5px;
text-align: center;
height: 50px;
background: transparent;
font-size: 20px;
font-weight: 200;
width: 100%;
border: none;
border-bottom: 4px solid #494848;
}
input[type="text"]:focus {
outline: none;
border: 2px solid #494848;
border-bottom: 4px solid #494848;
border-radius: 5px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.301);
}
index.js
app.post("/", function (req, res) {
const taskName = req.body.newTask;
if (taskName) {
const task = new Task({
name: taskName,
});
// Save the task using save method provided
// by mongoose. It returns a promise, in
// which we re-direct to home page. we write
// it in then block to make sure that
// we are redirected only when the save
// method finished executing without any
// error. Otherwise the item will be saved,
// after we were redirected, thus, it will look
// like the task was not added and thus we
// will have to reload to see the newly added
// task. Which can be exhausting.
task.save()
.then(() => {
res.redirect("/");
});
} else {
res.redirect("/");
}
});
index.js
app.post("/delete", function (req, res) {
const checkedItemId = req.body.checkbox;
Task.findByIdAndRemove(checkedItemId, function (err) {
if (!err) {
console.log("Successfully deleted checked item.");
res.redirect("/");
}
});
});
Javascript
app.listen(process.env.PORT || 3000, function () {console.log(“Server running at port 3000”);});
index.js
const express = require("express");
const bodyParser = require("body-parser");
const ejs = require("ejs");
const mongoose = require("mongoose");
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));
mongoose.connect(
"mongodb+srv://:@cluster0.g6nae.mongodb.net/todolistDB");
const taskSchema = {
name: {
type: String,
required: true
}
};
const Task = mongoose.model("Task", taskSchema);
app.set("view engine", "ejs");
app.get("/", function (req, res) {
let today = new Date();
let options = { weekday: "long", day: "numeric", month: "long" };
let day = today.toLocaleDateString("en-US", options);
Task.find({}, function(err, foundTasks){
if (err){
console.log(err)
}
else {
res.render("index", { today: day, tasks: foundTasks });
}
})
});
app.post("/", function (req, res) {
const taskName = req.body.newTask;
if(taskName){
const task = new Task({
name: taskName,
});
task.save().then(()=>{
res.redirect("/");
});
} else{
res.redirect("/");
}
});
app.post("/delete", function (req, res) {
const checkedItemId = req.body.checkbox;
Task.findByIdAndRemove(checkedItemId, function (err) {
if (!err) {
console.log("Successfully deleted checked item.");
res.redirect("/");
}
});
});
app.listen(process.env.PORT || 3000, function () {
console.log("Server running at port 3000");
});
说明:我们将已安装的包包含在一些常量中以在我们的应用程序中使用。 app是我们创建中间件和初始化包以在应用程序中使用的常量。首先,初始化 body-parser 以告诉 Nodejs 使用 body-parser 读取数据。 public 文件夹被声明为 static 以告诉 Nodejs 这是我们的静态文件(例如 CSS 和图像)所在的位置。最后,将 ejs设置为视图引擎,以便我们可以在ejs文件中编写前端代码。
注意:我们不需要mongodb ,因为它由mongoose负责。
第 5 步:现在我们要将我们的应用程序与云数据库 MongoDB Atlas 连接起来,并定义我们集合的基本结构。我们定义了我们存储的数据类型以及与之相关的其他特征。
index.js
// Make sure you did not use any special
// characters(e.g. @) in your user-name
// or password
mongoose.connect(
"mongodb+srv://:@cluster0.38u1b.mongodb.net/todoDB");
// Defining the schema or structure of
// a single item in mongodb
const taskSchema = {
name: {
type: String,
required: true
}
};
// Using the following code, node.js
// creates a collection named
// 'tasks' using the taskSchema
const Task = mongoose.model("Task", taskSchema);
说明:这里我们使用我们的mongoose常量通过在常量对象mongoose上传递 link to connect 方法来连接到我们的数据库。然后我们在mongodb中描述我们的 item 的蓝图,我们将它的数据类型设置为 String。模型方法使用此蓝图创建一个名为 items 的集合。我们需要写一个带标题大小写的单数单词, mongoose会自动将其转换为小写单词和复数单词。我们数据库 URL 末尾的单词“todoDB”是我给数据库起的名字。如果你愿意,你可以使用任何其他词。
我们数据库的 URL 是通过以下步骤从我们的 MongoDB Atlas 帐户接收的:
- 单击连接按钮。
- 选择第二个选项:连接您的应用程序。
- 复制 URL 格式。
第 6 步:现在我们处理我们的请求,我们处理的第一个请求是对我们主页的请求,发送到 URL:“/”。在这里,我们编写将我们的主页提供给我们主页的路由或路径的代码,即任何网站的“/”。主页只是展示了我们所有的任务。在此应用程序中,它是我们拥有的唯一网页,因为不需要任何其他页面。
index.js
app.get("/", function (req, res) {
// Getting today's date to display
// on top of our to-do
let today = new Date();
let options = {
weekday: "long",
day: "numeric",
month: "long"
};
// If we do not use the first argument
// in below line, which is "en-US" we get
// date in form of numbers only, separated
// with a /, thus the day won't be known
let day = today.toLocaleDateString("en-US", options);
// Find is a function given by mongoose, which
// is applied to a collection, it returns
// all the documents found in it.
Task.find({}, function (err, foundTasks) {
if (err) {
console.log(err)
}
else {
// Render the file names index.ejs and
// send the object to with the following
// data, sent as second parameter, in
// which we send date
// and tasks found in database.
res.render("index", { today: day, tasks: foundTasks });
}
})
});
解释: get请求是为了读取数据,当我们的应用程序的URL在浏览器中输入时,get请求被发送到“/”路由,也就是我们的主页,用于读取和查看我们主页的内容。 app.get()告诉它在“/”上接收到 get 请求时运行,方法是在其中运行回调。 find 是mongoose在每个集合上定义的一种方法,用于在该集合中查找文档。一个空对象被传递给这个 find函数来告诉没有条件找到的文档必须匹配,因此它表示要获取所有文档。如果没有发现错误,此函数返回的结果是一个对象数组。在响应对象上,我们调用 render函数,该函数接受要发送的ejs文件名和要发送到前端的值对象。
第 7 步:现在让我们进入“views”文件夹并创建一个ejs文件index.ejs ,在这里我们使用 HTML 代码定义网页的结构。我们还编写了一些 Javascript 逻辑来维护我们的应用程序的动态呈现。这种动态渲染只是在我们的页面上显示新添加的任务,而无需用户重新加载页面。
索引.ejs
To-do List
<%= today %>
<% if (tasks.length===0) {%>
No Task Added Yet
<% } else { %>
<% for (var i=0; i< tasks.length; i++){ %>
<% } %>
<% } %>
说明: ejs的语法类似于 HTML。 Javascript 语句写在 <% %> 内,变量写在 <%= %> 内。这种将 Javascript 与 HTML 结合使用的能力允许ejs具有动态呈现功能。在这个ej s 文件中,我们首先检查接收到的数组是否为空,如果不为空,则循环遍历这个对象数组,并在每个对象上使用点运算符来显示其名称。我们还在表单中的项目名称旁边放置一个复选框,该复选框在“/delete”路由上发送发布请求,并将其值设置为由 MongoDB 提供的对象的 id。 id 在 MongoDB 中写为_id 。我们希望在选中复选框时发送请求,因此使用 submit函数提交表单。
注意:确保变量的名称与渲染ejs 时从 Nodejs 发送到前端的对象的键匹配。
第 8 步:在 styles.css 文件中,我们编写 CSS 代码,让我们的主页看起来更漂亮。它使我们页面的主容器和标题出现在中心,我们定义了我们想要使用的颜色主题,还设置了我们页面上的字体、按钮和其他元素的样式。
样式.css
*{
font-family: cursive;
box-sizing: border-box;
}
h1 {
padding: 10px;
}
.box {
max-width: 450px;
margin: 20px auto;
background: white;
border-radius: 5px;
box-shadow: 7px 7px 15px 5px rgba(0, 0, 0, 0.3);
}
#heading {
background-color: #353434;
text-align: center;
}
.item {
min-height: 70px;
display: flex;
align-items: center;
border-bottom: 1px solid #F1F1F1;
}
.item:last-child {
border-bottom: 0;
}
input:checked+p {
text-decoration: line-through;
text-decoration-color: #353434;
}
input[type="checkbox"] {
appearance: none;
margin: 20px;
height: 25px;
width: 25px;
border: 2px solid black;
border-radius: 3px;
}
input[type="checkbox"]:hover,
input[type="checkbox"]:focus{
transform: scale(1.2);
background-color: #353434;
color: white;
cursor: pointer;
}
input[type="checkbox"]:checked{
clip-path: polygon(14% 44%, 0 65%,
50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
p {
text-align: left;
margin: 0;
padding: 20px;
font-size: 1.8rem;
font-weight: bold;
color: #353434;
text-shadow: 2px 2px gray;
}
form {
text-align: center;
padding: 10px;
}
button {
width: 50px;
height: 50px;
border-radius: 50%;
font-size: 1.5rem;
background-color: #494848;
border-top: 4px solid white;
border-left: 4px solid white;
border-bottom: 4px solid black;
border-right: 4px solid black;
color: white;
}
button:hover{
cursor: pointer;
color: #494848;
background-color: white;
border: 3px solid #494848;
box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.459);
}
input[type="text"] {
margin: 5px;
text-align: center;
height: 50px;
background: transparent;
font-size: 20px;
font-weight: 200;
width: 100%;
border: none;
border-bottom: 4px solid #494848;
}
input[type="text"]:focus {
outline: none;
border: 2px solid #494848;
border-bottom: 4px solid #494848;
border-radius: 5px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.301);
}
第 9 步:现在我们处理添加项目的路线。我们回到我们的 index.js 文件。现在我们将编写从前端读取传入数据的代码,将其作为新任务保存在数据库中,然后在成功添加任务后重定向回主页。我们还检查传入的数据,这里的任务,是否不为空。
index.js
app.post("/", function (req, res) {
const taskName = req.body.newTask;
if (taskName) {
const task = new Task({
name: taskName,
});
// Save the task using save method provided
// by mongoose. It returns a promise, in
// which we re-direct to home page. we write
// it in then block to make sure that
// we are redirected only when the save
// method finished executing without any
// error. Otherwise the item will be saved,
// after we were redirected, thus, it will look
// like the task was not added and thus we
// will have to reload to see the newly added
// task. Which can be exhausting.
task.save()
.then(() => {
res.redirect("/");
});
} else {
res.redirect("/");
}
});
解释:就像我们从一个类中创建一个对象一样,我们使用 new 关键字从集合中创建一个文档。然后我们调用常量itemName的save()方法,将它保存到我们的数据库中。使用请求对象读取它,然后访问它的主体,然后使用点运算符访问输入的名称,就像访问嵌套对象一样。这个函数也是由mongoose提供的。我们还在检查项目的名称是否为空。如果它是空的,我们重定向到家,如果不是,我们保存它然后重定向到家。然后 Nodejs 再次接收到主页的请求,即“/”,并为其运行中间件。这次找到了一个新项目,因此它发送到前端的数组被更新,从而导致我们的页面更新。这就是在我们的应用程序中建立动态渲染的方式。
第 10 步:最后,我们处理每次单击复选框时发送的删除请求。当我们点击一个复选框时页面将重新加载,在前端它被写在一个表单中,一旦提交一个复选框,它就会自动提交,因为传递给它的函数,并发送它放置的任务的id旁边的。以下代码读取 id 并从具有此 id 的数据库中查找任务并将其删除,并在任务完成后重定向回主页。
index.js
app.post("/delete", function (req, res) {
const checkedItemId = req.body.checkbox;
Task.findByIdAndRemove(checkedItemId, function (err) {
if (!err) {
console.log("Successfully deleted checked item.");
res.redirect("/");
}
});
});
说明:在我们表单的action属性中,我们使用“/delete”作为请求发送到的地址,方法属性的值设置为“POST” 。在这个表单里面是一个复选框,当我们点击它时它会提交表单。现在,这个带有 post 方法的请求在app.post()中被接收。我们存储从前端发送的对象的 id,然后在 items 集合上,我们调用mongoose提供的方法来查找与接收到的具有相同 id 的文档并将其删除。它需要一个 id 和一个回调作为参数。删除项目时运行回调。
第 11 步:最后,我们编写一个代码,使我们的应用程序可以从机器的一个端口访问。这叫做倾听。
Javascript
console.log(“运行在 3000 端口的服务器”);
});
说明:这是我们最后写的代码。在我们的应用程序对象上调用listen 方法来监听我们计算机上的端口。这里我们使用 3000,您也可以使用其他端口。 process.env.PORT是托管应用程序的服务使用的端口。他们将使用的端口不需要与我们在开发过程中使用的相同。
现在让我们看看完整的代码。
index.js
const express = require("express");
const bodyParser = require("body-parser");
const ejs = require("ejs");
const mongoose = require("mongoose");
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));
mongoose.connect(
"mongodb+srv://:@cluster0.g6nae.mongodb.net/todolistDB");
const taskSchema = {
name: {
type: String,
required: true
}
};
const Task = mongoose.model("Task", taskSchema);
app.set("view engine", "ejs");
app.get("/", function (req, res) {
let today = new Date();
let options = { weekday: "long", day: "numeric", month: "long" };
let day = today.toLocaleDateString("en-US", options);
Task.find({}, function(err, foundTasks){
if (err){
console.log(err)
}
else {
res.render("index", { today: day, tasks: foundTasks });
}
})
});
app.post("/", function (req, res) {
const taskName = req.body.newTask;
if(taskName){
const task = new Task({
name: taskName,
});
task.save().then(()=>{
res.redirect("/");
});
} else{
res.redirect("/");
}
});
app.post("/delete", function (req, res) {
const checkedItemId = req.body.checkbox;
Task.findByIdAndRemove(checkedItemId, function (err) {
if (!err) {
console.log("Successfully deleted checked item.");
res.redirect("/");
}
});
});
app.listen(process.env.PORT || 3000, function () {
console.log("Server running at port 3000");
});
运行应用程序的步骤:要运行应用程序,请打开终端并编写命令。
node index.js
输出:打开浏览器并在其 URL 地址框中输入:localhost:3000。
现在让我们了解如何部署上面创建的应用程序。
第 1 步:访问 Heroku 的网站。 http://www.heroku.com/
第 2 步:如果您还没有帐户,请注册。
第 3 步:填写注册表单
Sing Up 完成后,您将看到如下页面:
您将获得一个入门页面:
第 4 步:根据您的操作系统安装 Heroku CLI:
第 5 步:打开系统的 CLI,或当前项目目录中的 VS Code 终端。使用终端命令登录 Heroku:
heroku login
第6步:打开一个浏览器窗口,单击登录按钮,您就完成了。
注意:由于我们刚刚创建了一个帐户并在我们的浏览器中打开了 Heroku,它会识别我们并且不会要求提供登录凭据。如果我们也从浏览器中注销,那么它会要求输入电子邮件和密码,然后登录我们。
第7步:然后在我们的项目中初始化git。 (确保您的计算机系统中安装了 git:https://git-scm.com/downloads):编写命令。
git init
第 8 步:使用以下命令添加.gitignore文件和 Procfile:
touch .gitignore
创建 .gitignore 文件以列出不应包含在我们的存储库中的所有文件和文件夹。这些主要是node_modules文件夹,因为它可以通过编写命令在任何地方创建: npm install 。 该命令读取包含已安装包信息的 packages.json 文件和 packages-lock.json 文件,并自动安装在目录中。
第 9 步:现在在终端中,编写以下命令。
heroku create
git add .
git commit -m “Initial Commit”
git push heroku master
第 10 步:将出现一个 URL,即您的托管应用程序的 URL。
已部署应用程序链接: https://thawing-ravine-87998.herokuapp.com/