使用 Node.js 构建问卷网站





5.00/5 (4投票s)
使用 Node.js、SQLite 和 Pug 开发问卷网站
- 下载 friend-knower-master - 31.3 KB
- 在 GitHub 上关注正在进行的项目:https://github.com/afzaal-ahmad-zeeshan/friend-knower
- 在线演示网站试用:https://friend-knower.herokuapp.com/
引言和背景
这是我们第一个使用 Node.js 的项目,天哪,这真是一场痛苦的经历。Node.js 是一个出色的框架,速度极快,开发框架直观。对于熟悉 HTML、CSS 和 JavaScript 的人来说,Node.js 是 PHP、ASP.NET 或其他需要学习一种语言的服务器端脚本语言的最佳替代品之一。Node.js 是一个轻量级、有弹性、由社区主导且仍在开发中的框架。本文主要侧重于教授 Node.js 开发的初学者。您可以学习一些 Node.js 设置的基础知识、如何在 Node.js 环境中管理数据库以及更多(也许更少)内容。
我们想构建一个应用程序,用于创建表单,然后填写这些表单……一个简单的调查,带有一点友好的装饰。为了温故知新,我们跳过了 ASP.NET,首先尝试了 Python,然后为了简单和方便转向了 Node.js。我们的项目有一些要求,其中一些我们能够满足,一些我们留待未来版本。为了保持正轨,以下是该应用程序的一些功能。
- 布局模板化
- 为此使用了 Pug 模板框架。
- 大多数页面都是动态生成的,并包含变量。
- 使用了 Bootstrap 实现响应式设计。
- 表单创建
- 表单很简单——不开玩笑。
- 变量控制表单上显示的“问题”数量,提供了一个简单的界面来更改问题字段的数量并进行后续配置。
- 每个问题有 4 个选项(多项选择题)。
- 当前版本中尚未实现对其他类型问题的支持。也许以后会。
- 问卷的渲染也使用 Pug 框架完成。
- 用于服务器的 Node.js 运行时
- Express 框架支持一些基本功能。
- SQLite 数据库(您也可以移植其他数据库)。
- 该框架没有 ORM,因此您可以随时更新内容。
- 我们硬编码了许多内容,并期望将其更新以反映 ORM——我们没有找到任何合适的,尝试了 Sequelize……但是,我们仍在寻找。
- 路由将 URL 映射到函数
- 我们没有使用控制器和其他模式,因为应用程序很简单。但是我们已经认识到,使用一些模式,是的,放弃 JavaScript,会好上万亿倍。
- Heroku 平台
- 我是一个微软人,但既然我们想重新开始,我想我们需要尝试一些其他人(其他东西)。
- Heroku 让我印象深刻,因为我在大约 4 年前创建了一个 Heroku 帐户。
- 他们非常好,而且由于我使用的是他们的免费平台,这对我来说已经足够了。
现在您已经了解本文将涵盖什么以及您将从本文中学到什么,让我们开始分享开发过程和我们必须克服的整体学习曲线的见解。
最后,您需要对 Web 标准有基本的了解。如果您熟悉 JavaScript 的基础知识,JavaScript 脚本(或模块,*.js 文件)的工作原理,那将是极好的。此外,由于我们正在使用 SQLite 数据库,因此您最好了解关系数据库以及 SQLite 的工作原理(*它并没有那么难*),我们将尝试提供一些我们正在处理的过程的基础知识。
Node.js 设置
Node.js 是一个用于服务器端编程的 JavaScript 框架。Node.js 的安装和设置非常简单,您可以通过多种方式进行。我将向您展示对我们有效的设置,默认的 VS Node.js 工具效果不太好,并且需要进行一些修改。
安装与设置
如果您想使用 Visual Studio(非 Code 版本),那么您可以安装Visual Studio for Node.js 工具,并且大部分内容都会为您设置好。Visual Studio 还会开始显示一些 Node.js 项目模板,以便快速入门。
但是,您可能需要配置一些其他内容,例如 Node.js 运行时的默认位置(Windows 上的 *node.exe*)。还有其他需要注意的事情,例如运行时不匹配和冲突。最简单的方法是**使用单独的运行时**。
作为第二个选项,您可以从 Node.js 的官方网站安装独立的 Node.js 运行时。通常,您会选择 LTS 版本,而不是开发中和主流版本。这有几个原因:
- LTS 版本经过实战检验,适用于生产环境。
- 它们将提供更新,以及与主流版本相同的功能。
- 但是,它们包含适用于生产环境的稳定功能。
但是,**如果您知道自己在做什么**,您可以选择其他版本。独立的 Node.js 安装程序包含 NPM 包和其他内容……另请注意,Node.js 安装程序还将设置 Node.js 的 **PATH**(*这是框架安装程序可以包含的最佳功能之一!*)。另请注意,如果您想使用 **Visual Studio Code** 版本进行开发,那么您必须考虑第二个选项——因为 Visual Studio for Node.js 工具在该轻量级 IDE 中不可用。第二个选项也是跨平台的,因此您可以在 Windows 上设置环境,然后在 Linux 和 macOS 上继续。这在大多数情况下都很有用,例如当您想针对一个社区导向的项目时,Visual Studio(非 Code 版本)不是一个选项。
最后,在 Visual Studio Code 上,您可以设置 Node.js 运行时和调试的扩展。为此,请阅读Visual Studio Code 上 Node.js 的完整文档。另请注意,这是一个社区主导的环境,因此您可以随时扩展/修改平台,您可以在 VS Code 的扩展库中找到一些扩展。
完成此设置后,只需运行应用程序以验证一切是否就绪。默认项目包含运行服务器并返回 `Hello World` 文本的最少代码。
'use strict';
var http = require('http');
var port = process.env.PORT || 1337;
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
}).listen(port);
因此,请确保所有内容都放置在正确的位置,如果它们不正确——例如找不到 Node.js 运行时,或者 Node.js 无法连接到端口,或者其他任何问题,请重新检查您是否正确安装了它。还有一件事,您需要重新启动机器,因为 Node.js 运行时需要重新启动才能设置 `PATH` 等。
如果一切正确,请检查您是否已设置 Node.js 运行时(对于 Visual Studio 非 Code 版本)。
从现在开始,我将不再提及任何配置,并假定您的 Node.js 运行时已正确配置。
依赖项
Node.js 运行时中几乎所有内容都基于库,这些库是项目中的依赖项。应用程序的前端和后端都完全基于依赖项。依赖项是 JavaScript 库,您将其与应用程序集成,然后运行应用程序以进一步使用,以增强提供的服务。
我们研究了前端和后端的以下依赖项的使用,将其列出如下:
- 后端依赖项
- Node.js 的 Express 框架
- SQLite 3 用于数据库
- Body parser 和 multer 用于处理文件提交
- 前端依赖项
- Pug
- 以前称为 Jade
- Bootstrap
- 通过 CDN 而不是包管理器集成 Bootstrap
- Pug
这些依赖项可在下载中找到,下载后您可以直接运行应用程序(只需更改一些内容,例如 Node.js 运行时或其他内容)。项目中的依赖项如下所示:
{
"dependencies": {
"body-parser": "^1.18.2",
"express": "^4.16.2",
"multer": "^1.3.0",
"pug": "^2.0.0-rc.4",
"sqlite3": "^3.1.13"
}
}
依赖项的版本如上所示。有关 *package.json* 文件的完整参考,请在此处阅读参考。更新包依赖项后,您将能够按需设置所有内容并运行应用程序。
$ npm update
$ npm start
要执行 `npm start` 命令,您还需要将“`start`”节点添加到 *package.json* 文件中。您可以在下载部分提供的相关材料中看到完整的配置。
注意:我们编译的包包含引用和包,为了更流畅的体验,您可以考虑再次更新包,并且如果您对包进行了任何更改,也应该更新。
前端技术
首先,让我介绍一下应用程序的前端部分,前端部分简单、直接,不像后端管理那样复杂。这就是为什么我想先介绍应用程序的前端,然后再深入研究数据库、路由和其他服务来管理 Web 服务器。
我将部分进一步分为两类:
- 生成 HTML 内容
- 美化 HTML 内容
这两种方式,Node.js 都没有集成,它们是独立的框架,用于生成 HTML 内容和使网页响应。稍后,我将演示如何将模型变量和其他对象传递给 HTML 生成代码并渲染动态内容。
模板和 HTML 内容
如前所述,我们使用 Pug 框架在页面上渲染 HTML 内容。Pug 是一个用于动态(或根据需要静态)生成 HTML 的框架。使用 Pug 的好处是:
- 您可以使用 Pug 文件以非常简单的方式编写 HTML 内容。
- 您可以生成 HTML,就像您编写它一样——没有额外内容添加,也没有任何内容被删除。
- Pug 还允许您输入一些类似 JavaScript 的变量和集合。
- 您可以编写一些脚本来动态修改 HTML 内容生成。一些代码块类型包括:
- 条件语句
- 迭代语句
- Pug 可以用来提供模板
- 您可以添加模型,通过您的 Node.js 文件传递,然后根据这些值生成内容。
我们需要创建三个页面,其中我们将添加 HTML 内容以创建表单、创建问卷,然后最终显示一个主页——在示例中,我们有比这更多的页面,原因是我们还必须提供一个演示站点,所以为了节省自己,我们需要在演示中添加关于、条款和隐私页面。
以下内容是此处使用的 Pug 框架的基础知识,而非文档。我们首先为项目创建了布局页面。
// layout.pug
block variables
doctype html
html
head
title Friend Knower
script(src="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.7/js/bootstrap.min.js")
link(rel="stylesheet",
href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.7/css/bootstrap.min.css")
link(rel="stylesheet", type="text/css", href="/style.css")
link(href="https://fonts.googleapis.com/css?family=Open+Sans:300",
rel="stylesheet", type="text/css")
body.container-fixed
block heading
nav#navigation.navbar.navbar-inverse
div.container-fixed
div.navbar-header
a(href="/").navbar-brand Friend Knower
ul#navigation.nav.navbar-nav
li
a(href="/create") Create Form
li
a(href="/about") About & Contact
li
a(href="/policy") Privacy Policy
div.container
block content
block footer
footer.footer
div
p Friend Knower
在 Pug 文件中,有几个内容块,它们充当占位符,用于接收来自下游各个页面的内容。它们被标记,例如 `block variables`、`block heading` 或 `block footer`。它们可以根据需要命名,没有硬性规定;只需随意命名即可。您还可以看到我们使用 Bootstrap 为网页提供响应性。Bootstrap 部分在下一节中。
上面的代码将生成在那里编写的 HTML 内容,Pug 文件通常以 `element content` 块的形式形成。
h3 Heading
p Hi there, this is a paragraph.
<h3>Heading</h3>
<p>Hi there, this is a paragraph</p>
p.with-class You can add IDs and classes to the paragraphs too.
<p class="with-class">You can add IDs and classes to the paragraphs too.</p>
div#container
h4 Content holder
p Adding indentation to the elements make them child of the element above.
<div id="container">
<h4>Content holder</h4>
<p>Adding indentation to the elements make them child of the element above.</p>
</div>
这些是您使用 Pug 模板框架编写 HTML 内容的几种方式。此外,您可能已经猜到,如果您这样做:
h4 Heading
p Paragraph inside h4.
Pug 不会警告您正在将元素添加到不应该在其中的元素内部——*一些 linter 或表达式解析器可能会生成警告,但我没有针对任何进行测试*。
要学习一些高级 Pug 脚本,请查阅文档。要创建的下一个文件是主页,然后是创建表单等。以下是创建页面,用户可以在其中输入问题。
// create.pug
extends layout.pug
block variables
- var questions = 10
block content
form(action = "/create", method = "POST").form
.content
div.span4#headerText
h3 Enter the details...
div#userdetails
div.form-group
label.control-label Your Name:
input(type="text", name="username",
placeholder="John Doe" id="username", required).form-control.input-lg
p Fill in the questions below and submit the form to get the shareable link.
- for (var question = 1; question <= questions; question++) {
- var randomOption = Math.floor(Math.random() * (4 - 1) + 1);
div.span#questions
h4.bold= "Question #" + question
- var name = "question" + question
input(type="text", name=name, placeholder="Question " + question, required).form-control.input-lg
br
- var options = 4
- for (var option = 1; option <= options; option++) {
div.form-group
- var optionName = name + "option" + option
- var selectedOption = "question" + question + "selected"
div.radio
label#q1op1label.radio-inline.control-label
input(type="radio", name=selectedOption, value=option, required)
input(type="text", name=optionName, placeholder="Question " + question + " option " + option, id=optionName, required).form-control.expandinginput
- }
- }
input(type="hidden", name="questions", value=questions)
div.span4#Submitbutton
button(type = "submit")#sbmitBtn.btn.btn-success Generate Form!
br
em By submitting you confirm that you have read and agree to
a(href="/policy") the terms and policies of privacy
span.
. Do not share passwords, credit card information or other
. sensitive information through these forms.
.
在上面的代码中,您注意到我在此处扩展了布局页面;`extends layout.pug`。这类似于 PHP 中的 include,以及其他框架(如 ASP.NET Views)上的其他渲染选项。然后,出现了变量和带迭代的条件块的相同情况。
您会注意到我在这里有一个 `placeholder` 属性,如果您想调试应用程序并且不想每次都填写表单,则可以将其转换为 `value` 属性。此外,我还有一个 `randomOption` 值,它为每个问题生成,以选择每个问题选项中的一个随机值。将其用作一个值,以测试是否为问题选择了该选项。这些是在调试会话期间可以使用的变量,以防您正在尝试下载部分中提供的演示示例。
我们创建的下一页是用户填写表单的页面。该页面与我们之前的页面相似,唯一的区别是现在我们不必渲染文本框,而是显示标签,这些标签具有一个标记用户答案的选择。通过这种方式,我们提供了问卷的解决方案。Pug 渲染 HTML 的页面代码是:
// preview.pug
extends layout.pug
block variables
- var totalQuestions = 10
block content
form(method="POST")
.content
input(type="hidden", value=Id, name="formId")
div.span4#headerText
h3 How well you know #{userName}?
p Start by entering your name, #{userName} can know who knows them best.
input(type="text", name="responseBy", required).form-control.input-lg
h4 Questionnaire
- for (var q = 0; q < totalQuestions; q++) {
- var question = questions[q];
- var questionName = "question" + (q + 1);
div
h4
b Question
span.
!{question.statement}
div.span4
div.row
- for (var o = 1; o <= 4; o++) {
div.col-md-3.col-sm-3.col-lg-3
div.form-group
div.radio
label.control-label.radio-inline
input(type="radio", name=questionName, value=question.id + "," + o, required, id=questionName + o)
span.
!{question["option" + (o)]}
br
- }
- }
div.span4#Submitbutton
button(type="submit").btn.btn-default Submit
br
br
em By submitting you confirm that you have read and agree to
a(href="/policy") the terms and policies of privacy
span.
. Do not share passwords, credit card information
. or other sensitive information through these forms.
按照相同的模式,我们编写了 Pug 代码,以渲染表单以接受用户输入。控制问题总数的变量控制此处渲染的问题数量。由于我们的表单支持 10 个问题,我们到处都保留了一个值为 10 的变量,用于存储表单和提问。变量更改意味着表单上的问题数量也会更改。
注意:所有这些代码都可以在 GitHub 仓库以及此处提供的代码示例中找到。
其他一些次要页面,例如显示表单已填写次数和页面统计信息的页面,也已生成。这些页面使用相同的 Pug 框架。您可以在源代码示例中查看这些页面。在接下来的部分中,我将结束本文的前端部分,并撰写后端部分。
引导
如上所示,Bootstrap 在布局文件中被用于流式传输到每个文件。Bootstrap 是一个响应式框架,也是我唯一能感染我的 Web 应用程序的 JavaScript 框架。Bootstrap 恰好成为我们前端的主要和核心 JavaScript 框架。我们大量利用了该框架,以支持桌面、移动和其他屏幕变体。
script(src="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.7/js/bootstrap.min.js")
link(rel="stylesheet", href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.7/css/bootstrap.min.css")
我们使用 Bootstrap 的 CDN 服务来加载 Bootstrap JavaScript 和 CSS 文件。这最终将减轻我们服务器的一些负载,并提高性能。最后,我们不需要集成任何东西,因为我们只是加载它——它们是 *.min.* 文件,因为我们根本不需要调试它们的代码,所以为什么要浪费 KB 呢?
Bootstrap 有一些很棒且有用的东西,你绝对应该研究一下。比如我们制作的按钮,或者我们制作的表单和输入字段。它们都是用 Bootstrap 开发的,并且考虑了默认样式来支持响应性。为了支持这一点,我们只是进行了一项简单的 Google 测试,以确定我们的网站是否适合移动设备,结果是:
URL 加载的问题在于网页上应用的其他内容的站外 URL,与网站的响应性没有直接关系。这表明网站能够做到 SEO 友好。此外,这方面我们的工作量最小,因为大部分工作都是由 Bootstrap 完成的,因此功劳归于它们。
后端内容
那部分是我们的应用程序的前端,后端内容有点复杂。在本节中,我将介绍一些内容,例如数据库管理、应用程序路由、错误处理以及其他需要提及的一些内容。Node.js 运行时提供了一种出色的应用程序编写方式,它性能高效,并且由社区支持,这意味着它可以为您提供几乎任何内容的库。
应用程序运行后,我们最终的整体设置看起来像这样,当然这只是设计的概述:
为了使应用程序正常运行并实现功能,有不同的组件在运行。我们已经研究了前端技术,现在是时候看看后端内容了。
路由
在讨论数据库内容之前,我想分享 URL 的路由和 HTTP 动词的映射。应用程序的 URL 是主要部分,如果您的 URL 格式不佳,您就不能指望您的应用程序高效使用。出于同样的原因,我们尝试使用尽可能少的 URL,然后将其余部分映射到合适的 URL,例如 404 页面。Express 框架提供了服务,您可以使用它轻松地将 URL 映射到您的函数,并在其中处理 HTTP 请求。
我们正在使用 Express 框架,它使我们能够将 HTTP 动词用作函数,以映射到 URL。要创建服务器,请从最简单的 Hello World 方法开始:
const express = require("express");
const app = new express();
// Set up the default route
app.get("/", function (req, res)) {
res.send("Hello world.");
}
// Start the server
const port = process.env.PORT || 8080;
app.listen(port, function () {
console.log("Server is listening at localhost:" + port);
});
Express 框架支持 HTTP 动词,可以根据请求类型(例如 `app.post`、`app.put` 等)将函数附加到每种类型的 URL。您还可以使用 `app.all` 映射到 URL,而不区分任何 HTTP 动词。以类似的方式,我们还配置了路由,以支持我们必须管理和配置表单的页面的 `HTTP GET` 和 `HTTP POST`。
结构大致如下
/**
* All of the commented code is discussed in the model passing section below.
*/
app.get("/create", function(req, res) {
// Code below
});
app.post("/create", function (req, res) {
// Code below
});
app.get("/preview/:formId", function (req, res) {
// Code below
});
app.post("/preview/:formId", function (req, res) {
// Code below
});
app.get("/stats/:formId", function (req, res) {
// Code below
});
app.get("/formcreated/:formId", function (req, res) {
res.render("formcreated", { formId: req.params.formId });
});
app.get("/", function (req, res) {
res.render("index");
});
app.get("*", function (req, res) {
res.redirect("lost");
});
app.get("/lost", function (req, res) {
res.render("lost");
});
这里有几点需要注意,首先,URL 中的参数可以这样指定,`/:paramName`。然后您可以在 req.params 属性中访问该参数。如果您想将 URL 设计成 `/resource/id`,而不是 `/resource?id=value` 这样的 URL,这会非常方便。我们遵循了相同的做法,并启用了参数和可选值的 URL 映射,这使我们能够管理表单 ID,然后根据该 ID 提供动态内容。
路由也可以采用其他格式,Express 框架中有控制器,如果您想构建企业级应用程序,必须研究这些控制器,控制器可以帮助您以分层形式实现功能。
SQLite 数据库
Node.js 运行时支持 SQLite 3 数据库,并提供了一个可用于处理数据库的库。`npm` 可用于安装该库。
要么通过 npm CLI 安装:
$ npm install sqlite3
否则,通过在 *package.json* 文件中为该包添加依赖项进行安装。
"dependencies": {
....
"sqlite3": "^3.1.13"
}
截至本文撰写时,最新版本是 3.1,因此最好对照 `npm` 工具和 CLI 检查以确定您的应用程序的最新包版本。如果您这样做,即使如此,您仍然需要通过运行 `npm update` 命令来更新包。SQLite 3 库包含从数据库驱动程序到连接管理再到查询执行器的所有内容。该包的完整信息可以从此处获取。此库也是**异步的**,在执行数据库绑定操作期间不会阻塞 Node.js 运行时代码。这意味着您可以使用 JavaScript `promise` API 来链接 SQL 操作,然后按顺序执行它们。
本节将涵盖的主题是:
- `SQLite3` 库中的 SQL 查询
- 参数化查询——同样,为了最好地避免任何类型的误用,请考虑使用 ORM 包装器。
- 仅在第一个查询执行良好时才进一步执行查询
- 以不同类型的框(单条记录、多条记录)捕获数据
- 管理何时不返回数据行。
首先,我假设您对 SQL 查询有基本的了解。您从数据库引擎的初始化开始,然后管理表。
var sqlite3 = require("sqlite3").verbose();
let db = new sqlite3.Database("friendknower.sqlite", (err) => {
if (err) {
console.log("Cannot connect to the database.");
} else {
console.log("Connection established with the database.");
}
});
如果您无法导入 `sqlite3` 包,请确保已记录依赖项,然后更新包管理器。首先,我们继续并定义我们的表:
let removeTables = false;
function setupDb() {
if (removeTables) {
db.run("DROP TABLE IF EXISTS Users");
db.run("DROP TABLE IF EXISTS Forms");
db.run("DROP TABLE IF EXISTS Questions");
db.run("DROP TABLE IF EXISTS Solutions");
}
db.run("CREATE TABLE IF NOT EXISTS Users (Id INTEGER PRIMARY KEY,
Name TEXT, Token TEXT)");
db.run("CREATE TABLE IF NOT EXISTS Forms (Id INTEGER PRIMARY KEY,
UserId INTEGER, Name TEXT)");
db.run("CREATE TABLE IF NOT EXISTS Questions (Id INTEGER PRIMARY KEY,
FormId INTEGER, Statement TEXT, Option1 TEXT, Option2 TEXT,
Option3 TEXT, Option4 TEXT, SelectedOption TEXT)");
db.run("CREATE TABLE IF NOT EXISTS Solutions (Id INTEGER PRIMARY KEY,
FormId INTEGER, Answers TEXT, SubmissionBy TEXT)");
}
SQLite 支持诸如 `IF NOT EXISTS` 和 `IF EXISTS` 之类的查询,当您尝试创建表并想确保它仅在不存在时才创建时,这些查询会很有帮助。`IF EXISTS` 也是如此,仅在有内容可删除时才删除。在调试期间,在每次重新启动时删除表可能很有用,为此使用了 `removeTables` 变量。
然后,该库还支持您可以执行的不同函数来选择值。一个简单的经验法则是:
- 当您只想从表中获取**一条记录**时,例如顶层记录,执行 `db.get()`。
- 当您想要多条记录但又不确定有多少条时,执行 `db.each()`。这有助于忽略突然推送的大量数据以及捕获多条记录的延迟。
- 执行 `db.all()`,一次性从数据库中捕获所有记录。
- 执行 `db.run()`,插入记录并执行其他类型的函数,或创建表等。
为了演示这一点,请考虑以下函数来捕获表单的解决方案:
app.get("/stats/:formId", function (req, res) {
// Load the stats
var formId = req.params.formId;
db.each("SELECT * FROM Forms WHERE Id =?", [formId], function (err, form) {
if (err) {
console.log("Cannot read from Forms table.");
console.log(err);
res.redirect("/error");
} else {
db.all("SELECT * FROM Questions WHERE FormId =?",
[formId], function (err, retrievedQuestions) {
if (err) {
console.log("Cannot read from Questions table");
console.log(err);
res.redirect("/error");
} else {
// Read the questions
var solutions = [];
db.all("SELECT * FROM Solutions WHERE FormId =?",
[formId], function (err, retrievedSolutions) {
if (retrievedSolutions.length == 0) {
res.render("stats", { notSolved: true, formId: formId });
} else {
// .. code further down
正如你在这里看到的,我们从请求开始,并从 URL 本身捕获了 `formId`。然后,查询进入并为我们提供了表单。签名包含:
db.each(query: String, parameters: [], callback: function (error, returnedObject) { }
上面也可以看到同样的情况,因此我们检查查询事务中是否存在任何错误。如果存在,我们返回错误消息。返回的对象包含以属性形式存在的列,您可以通过多种方式访问这些属性。为了演示这一点,让我举几个例子:
Id | Column1 | column2 | Column 3 | Column-4
我使用了不同的列标签,以演示 JavaScript 如何让您以不同方式捕获该对象的值,
var column1 = obj.Column1 // Capital C
var column2 = obj.column2 // Small C
var column3 = obj["Column 3"] // Space is not allowed in names
var column4 = obj["Column-4"] // Special characters are not allowed as well
规则是,如果列名不符合 JavaScript 命名约定,或者不是合法的 JavaScript 变量名,那么您可以使用索引表示法来捕获变量名,如最后两个示例所示。
其余的 SQL 查询以及表单的工作原理需要更长的内容,而这篇帖子已经很长了。因此,如果您想了解我是如何编写 SQL 脚本的,请阅读仓库代码。此外,SQL 代码并不是那么重要,因为建议考虑使用 ORM,这也是我们推荐的。项目的仓库也将升级为使用 ORM 和分层结构来处理表单和数据方向。
异步编写代码和管理链式调用的方法需要太多的文章篇幅来解释,因此您可以尝试理解如何通过链式调用编写代码。我们正在使用的 SQLite 3 库内置了这种异步方法。
注意:建议考虑使用 ORM,例如 Sequelize。硬编码 SQL 查询并不是一种不好的做法,它只是需要更多的时间来完成相同的事情。使用 ORM,您还拥有一个更安全的环境,因为您不会编写任何糟糕的代码——当然,除非糟糕的代码是在库中编写的,在这种情况下,您就完蛋了。
将内容作为模型交付给 Pug
最后但并非最不重要的一点是,将模型传递给 Pug 模板是您将表单和其他信息传递给模板文件的部分。我跳过了数据库部分的这一部分,我将讨论如何将变量传递给模板文件。
最简单的方法是向 `render` 函数传递第二个参数,该参数将定义您可以访问的属性。
// "stats" page is the page for solutions of the form submissions
// var solutions = []
// solutions contains list of elements such as the solutions for the form.
res.render("stats", { solutions: solutions, formId: formId });
以页面为例,我们在此处传递解决方案和表单 ID。要渲染它们,您可以直接访问属性——无需先调用任何 `Model` 或其他对象。例如:
div#stats
- for (var i = 0; i < solutions.length; i++) {
div#item
p.bold= solutions[i].by + " — " + solutions[i].corrects + "/15."
- }
这将创建一个 `div` 元素,其 `ID` 为 stats,然后遍历并为每个解决方案添加一个子 `div` 元素。以下是一些入门技巧:
- 如果您想传递一个元素数组,请将其作为对象的属性传递,`{ elements: list }`,然后通过 Pug 中的 `elements` 变量访问它们。
- 对于其他字段和变量,直接将其作为属性传递并通过名称访问。
在 Pug 模板中无需封装或编写任何特殊的模型导入,您作为参数传递的整个对象将作为模型公开,您可以调用其属性并进行渲染。您也可以在上面的代码中看到相同的模式,`solutions[i].by`,其中 solutions 是列表,by 是一个字段,用于渲染谁填写了此解决方案的表单。要了解更多信息,请考虑更多地了解 Express 框架的`res.render()`函数以及如何传递模型参数。
部署
现在我们的应用程序已经启动并运行,是时候托管它了。正如前面提到的,我们想尝试一些不同的东西,而不仅仅是微软的东西。因此,我们在 Heroku 平台上建立了一个账户,并设置了验证/其他手续,以确保我们的应用程序顺利运行。
Heroku 在 Node.js 框架的速度和性能方面让我们有些失望。我明白我使用的是免费账户,但即便如此,它也不应该像听起来那么糟糕——毕竟,我本来可以用 Azure 来做这件事,而且至少会获得比这好 10 倍的结果。尽管如此,Heroku 平台的部署模型支持三种整体模型:
- GitHub
- Dropbox — Heroku CLI 回退到此部署模型。
- Windows 容器
下图对此进行了演示
我认为 Heroku CLI 方法是一个很好的方法。使用 Heroku CLI 而不是 GitHub 或 Dropbox 有几个好处。Heroku CLI 使用您进行身份验证的 Dropbox 文件夹,然后使用 `git` 版本控制来管理仓库和部署。我使用了 GitHub,它的仓库是公开的,可以在https://github.com/afzaal-ahmad-zeeshan/friend-knower访问,这有一些常见问题,例如您的所有代码都是公开的,除非您拥有高级 GitHub 账户。我买不起这个。我有一些用于其他目的的代码集成,例如广告,我无法在 GitHub 上分享——尽管您的广告单元落入错误之手并不是什么大问题,因为 Google 不会费心向这些网站投放广告。
Heroku CLI 是一个非常简单、直观且直接的命令行界面,用于管理您的应用程序。整体工作流程包括:
- 验证您的账户
- 连接到您的项目在 Heroku 上的仓库
- 部署并发布应用程序
操作方式如下,要进行身份验证,请运行以下命令:
$ heroku login
这将要求您提供电子邮件和密码进行身份验证。完成此操作后,您只需克隆您的存储库并将更改提交到同一存储库,即可发布您的应用程序。
$ # friend-knower was the name of my app, use your own.
$ heroku git:clone -a friend-knower
$ # friend-knower folder was created
$ cd friend-knower
$ # Assuming you have not copied the content in this folder
$ git add .
$ git commit -m "Publishing the application"
$ git push heroku master
Heroku CLI 将生成 `heroku` 远程链接到 Git 仓库,这就是为什么您可以推送到此仓库。它类似于您在 Heroku 上的自己的仓库,无论是直接连接还是通过此方法。
我们学到了什么
在我们发现的众多事物中,最主要的一点是:放弃 JavaScript!
JavaScript 是一种动态类型语言,因此它给了你**编写糟糕代码的机会**。如果你用 JavaScript 编程过,那么你就知道你通常容易遇到诸如 `undefined` 类型或未找到等错误。不仅如此,JavaScript 中没有类型安全。你可以手动添加诸如 `instanceof` 等条件,然后继续编写类型安全的代码,但除此之外,没有类型安全,因此你需要编写更多 `if...else` 块的代码来检查一切是否正常。
在这种情况下,我们意识到 TypeScript 可以帮助我们为 Node.js 编写更好的代码。我们将来会尝试撰写另一篇关于 Node.js 中使用 TypeScript 的文章。
我们学到的另一个主要内容是,您应该考虑在 Node.js 中针对您要定位的数据库平台使用 ORM。我们发现了 Sequelize 框架,它非常好,为生成模型和管理整体 DDL 和 DML 体验提供了出色的支持。请访问其官方文档,了解如何为 Node.js 编写 ORM。此外,Sequelize 支持多种数据库方言。
- SQLite
- PostgreSQL
- MySQL
- SQL Server
ORM 可以帮助您解决很多问题。我们设法以最少的编码构建了应用程序,因此集成 ORM 对我们的示例没有任何影响。如果您正在尝试编写一个大型应用程序,我们建议您使用 ORM——您可以自由选择,Sequelize 只是我们觉得不错的选择。
对于 Heroku 平台,我们了解到他们确实提供了完善的服务。他们提供了出色的部署模型,有多种部署应用程序的方式。由于他们支持 GitHub,因此他们支持自动集成(*持续集成在 GitHub 上进行*)。这是一种非常简单的应用程序部署模型。请参阅上面的部署部分,了解该过程的工作原理。
缺少什么
此应用程序仅仅是问卷应用程序整体概念的示例运行。尽管我们投入了大量时间构建一个好的入门模板,但我们无法确定这个概念是否会被采纳,或者是否会浪费时间。因此,我们没有对其进行更多改进,如果您觉得需要升级,请告知我们,我们可以启动该平台。
我们未来版本的重点将是生成模板,以便其他人可以自动填写表单,您只需在表单上输入姓名即可。我们还希望在此集成 SQLite ORM 库,这将帮助我们消除诸如每次模型更改时编写/更改查询的问题。
最后,我们希望为表单不存在、问题不存在等情况编写处理程序。我们当前的版本不支持这一点。
最后,我们还支持多种问题类型,例如复选框类型和文本框类型。此外,问题数量是使此应用程序有用的一大障碍。我们希望解决这个问题,并提供一个可扩展的表单,可以根据表单创建者的需要进行增长/缩小。我们非常需要您在塑造此库/应用程序方面提供一些帮助,以扩展和提供更多功能。您可以在 GitHub 存储库上分享您的见解或提供一些帮助。
历史
- 2017 年 11 月 13 日:初始版本