Node.js : 快速路线,从数据库中查询数据并在 template.hbs 视图中呈现
Node.js : express route, query data from database and render in template.hbs view
我想创建一个 Node.js 应用程序来对某些网站进行网络抓取,将数据保存在 PostgreSQL 数据库中,然后在网页上显示此数据的可视化(在 D3.js 中)。
我考虑过拆分前端部分(创建和显示可视化效果)和后端部分(进行网络抓取和更新数据库)。
两个应用程序的骨架(有两个是因为我把任务分成两个应用程序)如下。
后端应用程序(scraper
):
- 连接到数据库
- 如果表不存在则创建表
- 数据抓取器
- 在数据库上保存数据
- 与数据库断开连接。
这个后端应用程序每年只能启动几次(为此,如果使用 Unix,我可以配置一个 CRON 文件)。
前端应用程序(viz
):
- 连接到数据库
- 启动一个正在等待端口 3000 的服务器(我需要它来进行可视化)
- 每次用户刷新页面 (
onLoad()
) 时,应用程序都会进行查询 (SELECT
),从数据库中获取数据。通过这种方式,数据总是更新的。
此应用程序仅由程序员启动一次(理想情况下)。
我创建了这种类型的文件夹结构(我使用了 npm init
和 Express
):
project
|_ scraper
|_ helpers // contains some useful .js files
|_ elaborateJson.js
|_ saveOnDb.js
|_ utilFunc.js
|_ node_modules // modules installed using `npm install moduleName --save`
|_ routes // contains the files that make scraping
|_ downloaderHome.js
|_ downloaderWork.js
|_ services // contains a files concerning the db
|_ postgreSQLlib.js
|_ app.js
|_ package.json
|_ package-lock.json
|_ viz
|_ helpers // // contains some useful .js files
|_ utilFunc.js
|_ node_modules // modules installed using `npm install moduleName --save`
|_ public // contains files for visualizations
|_ index.handlebars
|_ script.js
|_ style.css
|_ services // contains a file concerning the db
|_ postgreSQLlib.js
|_ app.js
|_ package.json
|_ package-lock.json
有了这个结构,我已经有两个不知道如何解决的问题:
1. postgreSQLlib.js
文件(以及 utilFunc.js
)在 scraper
和 viz
中是相同的。如何避免代码重复?
2. 我必须在 scraper
和 viz
文件夹中安装一些模块(例如 express-handlebars
和 express
)两次。
这是project/scraper/app.js
:
const downloaderHome = require('./routes/downloaderHome.js');
const downloaderWork = require('./routes/downloaderWork.js');
const postgreSQLlib = require('./services/postgreSQLlib.js');
const saveOnDb = require('./helpers/saveOnDb.js');
const utilFunc = require('./helpers/utilFunc.js');
const express = require('express');
const exphbs = require('express-handlebars');
var app = express();
start();
async function start() {
console.log('\n Connect to db');
await postgreSQLlib.connect();
console.log('\n Create tables if they do not exist');
await postgreSQLlib.createHomeTable();
await postgreSQLlib.createWorkTable();
console.log('\n Check if table \'home\' is updated or not');
if(!await utilFunc.isTableUpdated('home', 6418)) { // 6308
console.log('\n Download data for home');
await downloaderHome.download();
console.log('\n Saving data for home on db');
await saveOnDb.saveHome();
}
console.log('\n Check if table \'work\' is updated or not');
if(!await utilFunc.isTableUpdated('work', 6804)) {
console.log('\n Download data for work');
await downloaderWork.download();
console.log('\n Saving data for work on db');
await saveOnDb.saveWork();
}
console.log('\n Disconnect from db');
await postgreSQLlib.disconnect();
}
这是project/viz/app.js
:
const postgreSQLlib = require('./services/postgreSQLlib.js');
const utilFunc = require('./helpers/utilFunc.js');
const express = require('express');
const exphbs = require('express-handlebars');
const http = require('http');
var app = express();
var response;
var callback;
start();
async function start() {
console.log('\n Connect to db');
await postgreSQLlib.connect();
// how do I check when page is refreshed?!
http.get({
hostname: 'localhost',
port: 3000,
path: '/',
agent: false
}, callback);
callback = function(res) {
response = res;
console.log(response); // here response will return an object
console.log('refresh callback');
}
console.log(response);
console.log('refresh');
///////////////////////////////////////////////
// How do I check the disconnection from the db?
// If I disconnect now, the visualizations are no longer work.
// So when do I get disconnected?
// Create problems leaving the connection to the active db?
///////////////////////////////////////////////
//console.log('\n Disconnect from db');
//await postgreSQLlib.disconnect();
}
第一个应用程序 (project/scraper/app.js
) 运行完美。
第二次申请(project/viz/app.js
)号我希望你这样做:
- 连接到数据库[完成。有效]
- 启动一个正在等待端口 3000 的服务器(我需要它来进行可视化)[我该怎么做?往下看(*)]
- 每次用户刷新页面 (
onLoad()
) 时,应用程序都会进行查询 (SELECT
),从数据库中获取数据 [我该怎么做?]
(*) 我想到了这样的事情:
async function start() {
console.log('\n Connect to db');
await postgreSQLlib.connect();
console.log('\n Get data from db');
var dataHome = await postgreSQLlib.getTableHome();
var dataWork = await postgreSQLlib.getTableWork();
//console.log('\n Connect to my server');
pageLoad(dataHome, dataWork);
}
function pageLoad(dataHome, dataWork) {
var hbs = exphbs.create({
helpers: {
getDataHome: function() {
return JSON.stringify(dataHome);
},
getDataWork: function() {
return JSON.stringify(dataWork);
}
}
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.get('/', function(req, res, next) {
res.render('index', { // index is html filename
showTitle: true,
});
});
console.log('Go to http://localhost:3000/ to see visualizations');
app.listen(3000);
}
其中 dataHome
和 dataWork
是两个对象,它们包含使用 SELECT
查询从数据库下载的数据。
但是通过这种方式,数据只会被废弃一次,而不是每次用户刷新页面时都会被废弃。
帮助将不胜感激。谢谢!
编辑
你能说得更准确些吗?我尝试这样做,但它不起作用:
project/viz/app.js:
const postgreSQLlib = require('../shared_libs/postgreSQLlib.js');
const express = require('express');
var app = express();
start();
async function start() {
console.log('Connect to db');
await postgreSQLlib.connect();
app.get('/', fetchFreshData);
}
async function fetchFreshData(req, res) {
// download data from db
var dataHome = await postgreSQLlib.getTableHome();
var dataWork = await postgreSQLlib.getTableWork();
// fill this JSON using the results
var viewData = {dataHome, dataWork};
// pass data to view
res.render('index', viewData);
}
project\viz\view\index.车把:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Map</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head>
<body>
<div id='example'></div>
</body>
<script src='/script.js'></script>
</html>
project\viz\view\script.js:
console.log('viewData:', viewData);
我哪里错了?
编辑 2
好的,我再次修改viz/app.js
代码:
const postgreSQLlib = require('../shared_libs/postgreSQLlib.js');
const express = require('express');
const exphbs = require('express-handlebars');
var app = express();
start();
async function start() {
await postgreSQLlib.connect();
var hbs = Handlebars.registerHelper('json', function(context) {
return JSON.stringify(context);
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.get('/', fetchFreshData);
console.log('Go to http://localhost:3000/ to see data');
app.listen(3000);
}
async function fetchFreshData(req, res) {
// download data from db
var dataHome = await postgreSQLlib.getTableHome();
var dataWork = await postgreSQLlib.getTableWork();
// fill this JSON using the results
var viewData = {};
viewData.timestamp = Date.now();
viewData.entries = dataHome;
// pass data to view
res.render('index', viewData);
}
当我 运行 应用程序时,没有错误,但如果我连接到 http://localhost:3000/,浏览器会告诉我无法访问该站点。我觉得有点傻...
编辑 3
如果我对你的代码理解正确,那么你的代码中有一个(令人分心的)错误。
在 returnOBJ()
而不是 res.render('index', viewData);
中,它应该是 res.render('obj', viewData);
(与 obj.hbs
文件相关)。对吗?
我这样修改index.hbs文件:
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Index</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head>
<body>
<h1>INDEX<small>{{timestamp}}</small></h1>
</body>
<script>
// add global variables in the .hbs file
window.viewData_dataWork = {{ json entries }}
console.log(window.viewData);
</script>
<script src='/script.js'></script>
</html>
但我得到:
(node:207156) UnhandledPromiseRejectionWarning: Error: callback function required
at Function.engine (C:\...\node_modules\express\lib\application.js:295:11)
at start (C:\...\viz\app.js:20:6)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:182:7)
(node:207156) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:207156) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
这段代码我也看不懂
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express);
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
为什么用不同的值两次调用 app.set('view engine', ...)
?
编辑 4
我进一步简化了代码:
/viz/app.js:
const postgreSQLlib = require(__dirname + './../shared_libs/services/postgreSQLlib.js');
const express = require('express');
const hbs = require('hbs');
var app = express();
// Server initiator
async function start() {
await postgreSQLlib.connect();
// hbs
app.set('views', '' + __dirname + '/views');
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express);
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
// router
app.get('/', testMe);
console.log('Go to http://localhost:3000/ to see data');
app.listen(3000);
}
// Your section with fresh data has been populated properly
async function testMe(req, res) {
console.log('testMe');
// fill this JSON using the results
var viewData = {};
viewData.data = 'this string';
// pass data to view
res.render('test', viewData);
}
// start the server
start();
/viz/views/test.hbs:
<html>
<head>
<title>Server test</title>
</head>
<body>
{{data}}
</body>
</html>
然后在提示命令中我转到 project/viz
并键入 node app.js
+ 回车。
进程启动并等待:没有错误。
当我转到 http://localhost:3000/
但我得到 Connection failed.
我快疯了
编辑 5
问题不是 connect
也不是构成 select 的函数,所以我稍微简化了代码。
现在,几乎可以正常工作了!
这是代码。
viz/app.js:
const postgreSQLlib = require(__dirname + './../shared_libs/services/postgreSQLlib.js');
const express = require('express');
var app = express()
const hbs = require('hbs');
const webapp_opts = {"port":3000};
Initialize();
//.: Setup & Start Server
async function Initialize(){
await postgreSQLlib.connect();
console.log("[~] starting ...")
//:[HBS]:Setup
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express)
app.set('views', "" + __dirname + "/views")
//:[HBS]:Helpers
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
})
//:[EXPRESS]:Router.Paths
app.get("/", IndexPathFunction);
// app.get("/script.js", scriptFile); <-- for script.js file
//:[EXPRESS]:Start
app.listen(webapp_opts.port,()=>{
console.log("[i] ready & listening","\n http://localhost:"+webapp_opts.port+"/")
})
}
/*async function scriptFile(req, res) { <-- for script.js file
console.log('\nscriptFile');
var viewData = {};
viewData.number = 50;
console.log('viewData:', viewData);
res.render('script.js', viewData);
}*/
//.: Router Function : "/"
async function IndexPathFunction(req,res){
var viewData = {};
viewData.timestamp = Date.now();
viewData.exJson = [{color: 'red', year: '1955'}, {color: 'blue', year: '2000'}, {color: 'yellow', year: '2013'}];
viewData.exString = 'example of string';
console.log('viewData:', viewData);
res.render('index', viewData);
}
viz/views/index.hbs:
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Index</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head>
<body>
<h1>INDEX timestamp: <small>{{timestamp}}</small></h1>
</body>
<script>
viewData = {};
console.log('viewData:', viewData);
viewData.exJson = JSON.parse('{{ json exJson }}'.replace(/"/g, '"').replace(/</, ''));
viewData.timestamp = {{timestamp}}; // doesn't work
viewData.exString = {{ exString }}; // doesn't work
console.log('viewData.exJson:', viewData.exJson);
console.log('viewData.timestamp:', viewData.timestamp);
console.log('viewData.exString:', viewData.exString);
</script>
<!--<script src='/script.js'></script>-->
</html>
问题是获取的数据类型不是 json。例如,当我尝试打印时间戳和 exString 时,它会给我错误。为什么?
此外,我想稍微清理一下代码,将 javascript 部分放入 script.js
文件中,该文件由 index.hbs
使用 <script src='/script.js'></script>
调用。
编辑 6
我发现 this tutorial 这对我很有用。
我通过添加一个css文件、一个图像和一个脚本来编辑index.hbs
文件(它只包含一个console.log('here');
但我的想法是放在script.js viewData
变量)。
项目/viz/views/index.hbs:
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Index</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<img src="/images/logo.png"/>
<h1>timestamp: <small>{{timestamp}}</small></h1>
<h2>Welcome in index.hbs</h2>
</body>
<script>
viewData = {};
console.log('viewData:', viewData);
viewData.exJson = JSON.parse('{{json exJson }}'.replace(/"/g, '"').replace(/</, ''));
viewData.timestamp = {{timestamp}};
viewData.exString = '{{exString}}';
console.log('viewData.exJson:', viewData.exJson);
console.log('viewData.timestamp:', viewData.timestamp);
console.log('viewData.exString:', viewData.exString);
</script>
<link href='/script/script.js' rel='script'>
</html>
我的文件结构是:
project
|_ node_modules
|_ scraper
|_ shared_libs
|_ viz
|_ app.js
|_ public
|_ css
|_ style.css
|_ images
|_ logo.png
|_ script
|_ script.js
|_ views
|_ index.hbs
现在我看到了图像并且使用了 css。但是脚本似乎不起作用,因为它没有在此处打印字符串。
我在互联网上搜索如何将变量从脚本标签传递到外部 js 文件,但我似乎没有找到适合我的东西。
我已经阅读了 handlebar API,但它们没有用。
共享(自定义)代码的解决方案:
With this structure I already have two problems that I don't know how
to solve:
- the
postgreSQLlib.js
file (and also utilFunc.js
) is the same in both scraper and viz. How can I avoid this duplication of the code?
您已经在使用 require
,因此请从 :
移动文件
project/scraper/services/postgreSQLlib.js
project/viz/services/postgreSQLlib.js
到项目根目录下新创建的目录
project
|_ shared_libs
|_ scraper
|_ viz
(在这个例子中我使用了shared_libs
,你可以随意命名)
project/shared_libs/postgreSQLlib.js
然后,来自代码:
const postgreSQLlib = require(__dirname+"/../shared_libs/postgreSQLlib.js");
这样您就不需要在 2 个位置的 2 个不同文件中维护代码
解决共享 node_modules :
同样适用于node_modules
简单地说,合并您当前的两个(node_modules 目录)
project/scraper/node_modules
project/viz/node_modules
进入 project
目录
根目录中的文件夹
project/node_modules
那么我的建议:
- 删除里面旧的(
project/scraper
&project/viz
)
- 从现在开始,使用
project
目录中的 npm i <module> --save
project/scraper
和 project/viz
都将使用 project/node_modules
无需复制整个库...
project/viz/app.js
的解决方案
如果你想在每个GET
请求上从数据库中获取数据
然后您必须在请求中包含 从 DB 中获取数据的逻辑:
app.get('/',FetchFreshData)
该函数将包含获取和构建 hbs 的 viewData,以使用 .hbs 标记中引用的新数据呈现
function FetchFreshData(req,res){
/* add your SELECT here */
var viewData = {} //fill this JSON using the results
res.render('index',viewData)
}
因此从逻辑上讲,每次您对“/”路由执行 GET
时,您将 运行 查询并接收包含新数据的“可视化”。
编辑:扩展答案
首先,我建议您更深入地了解车把 (hbs)。
如果您想在脚本中使用数据,您必须在服务器端注册一个帮助程序来根据需要呈现数据。
Handlebars.registerHelper('json',function(context){
return JSON.stringify(context)
})
我需要为你的情况做一个例子,假设 viewData 如下:
function FetchFreshData(req,res){
/* add your SELECT here */
var viewData = {} //fill this JSON using the results
viewData.timestamp = Date.now()
viewData.entries = dataHome
res.render('index',viewData)
}
会给我们(一个例子):
{
"timestamp":"1525182734",
"entries":[
{"name":"Entry 1"},
{"name":"Entry 2"},
{"name":"Entry 3"}
]
}
现在我将专注于您的模板 (.hbs) :
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Map</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head><body>
<h1>MAP<small>{{timestamp}}</small></h1>
<div id='example'>
{{#if entries}}
<ul>
{{#each entries}}
<li> this.name </li>
{{/each}}
</ul>
{{else}} No content... {{/if}}
</div>
</body>
<script>
//Add global variables in the .hbs file
window.viewData_entries = {{ json entries }}
</script>
<script src='/script.js'></script>
</html>
它将使用 JSON 对象(viewData)的“键”
访问数据并呈现视图...
编辑 2:尝试一些简单的东西
使用模块:hbs
并尝试使用我的 2 个简单示例,return 一个时间戳和一个对象,我还在某些部分修复了您的代码,以便您继续改进它更能满足您的需求。
const postgreSQLlib = require('../shared_libs/postgreSQLlib.js');
const express = require('express');
const hbs = require('hbs')
var app = express();
//:Server Initiator
async function start() {
await postgreSQLlib.connect();
//:HBS:
app.set('views', "" + __dirname + "/views");
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express);
hbs.registerHelper('json',function(context){return JSON.stringify(context)});
//:Router:
app.get('/', fetchFreshData);
app.get('/timestamp', returnTimestamp);
app.get('/obj', returnOBJ);
console.log('Go to http://localhost:3000/ to see data');
app.listen(3000);
}
//:Your section with fresh data has been populated properly
async function fetchFreshData(req, res) {
var viewData = {};
viewData.timestamp = Date.now();
viewData.dataWork = await postgreSQLlib.getTableWork();
viewData.dataHome = await postgreSQLlib.getTableHome();
// pass data to view
res.render('index', viewData);
}
/*
The index.hbs I posted earlier wont work anymore, since the "entries" key doesnt exist now... Try to understand the template engine with the following 2 examples below
*/
//:Simple Timestamp EXAMPLE
function returnTimestamp(req, res) {
var viewData = {};
viewData.timestamp = Date.now();
// pass data to view
res.render('timestamp_example', viewData);
}
/* This would be timestamp_example.hbs :
<html><head><title>Server Timestamp</title></head><body>{{timestamp}}</body></html>
*/
//:Simple JSON EXAMPLE
function returnOBJ(req, res) {
var viewData = {};
viewData.OBJ = {"key":"value"};
// pass data to view
res.render('json_example', viewData);
}
/* This would be json_example.hbs :
<html><head><title>Server Object</title></head><body>Page will alert "{{OBJ.key}}"<script>var OBJ = {{json OBJ}}; alert(OBJ.key);</script></body></html>
*/
//start the server :
start()
请记住,您需要为 /timestamp
和 /obj
路径添加新视图,我在每个服务器函数下方添加了一个注释示例。
----------
编辑 3:带回调的简单演示
const express = require('express'); var app = express()
const hbs = require('hbs')
const webapp_opts = {"port":3000}
//.: Setup & Start Server
function Initialize(){
console.log("[~] starting ...")
//:[HBS]:Setup
app.set('view engine', 'hbs'); app.engine('hbs', hbs.__express)
app.set('views', "" + __dirname + "/views")
//:[HBS]:Helpers
hbs.registerHelper('json',function(context){return JSON.stringify(context)})
//:[EXPRESS]:Router.Paths
app.get("/",IndexPathFunction)
app.get("/debug",(req,res)=>{
console.log("[GET]:/debug"); res.send("ok")
})
//:[EXPRESS]:Start
app.listen(webapp_opts.port,()=>{
console.log("[i] ready & listening","\n http://localhost:"+webapp_opts.port+"/")
})
}
//.: Router Function : "/"
function IndexPathFunction(req,res){
DBQuery((query_error,query_results)=>{
if(query_error){console.log("[!] DBQuery @ path : '/'\n",query_error.stack)}
else{
console.log("[+] DBResults :",query_results)
res.render("index",{data:query_results})
/*or*/// var viewData = {}; viewData.data=query_results; res.render("index",viewData)
}
})
}
//:[DB]:Example Query using callback method
function DBQuery(callback_function){
console.log("[>] DBQuery")
pool.connect((err,client)=>{
if (err) throw err
client.query('SELECT * FROM whatever',(err,res)=>{
client.release()
callback_function(err,res)
})
})
}
//------------------------------------
/* We can Initialize() the webapp
once we know the DB is accesible : */
const pg = require('pg')
const db_opts = {
"user":"dbuser","password":"secretpassword",
"host":"database.server.com", "port":3211,
"database":"mydb"
})
var pool = new pg.Pool(db_opts);
pool.on('error',(err,client)=>{
console.error('Unexpected error on idle client', err); process.exit(-1)
})
pool.connect((err,client,done)=>{
if(err){console.log("[!] DB Connection Error",err)}
else{console.log("[+] DB Connected"); client.release(); Initialize() }
})
请记住,了解问题所在的关键是控制流程并从小部件构建到大部件。
您应该阅读一下:
https://node-postgres.com/ , specially this part if you want to use Express with async/await
无论如何,这个新编辑的部分应该为您提供一些关于制作最小工作版本的见解。 ;)
我想创建一个 Node.js 应用程序来对某些网站进行网络抓取,将数据保存在 PostgreSQL 数据库中,然后在网页上显示此数据的可视化(在 D3.js 中)。 我考虑过拆分前端部分(创建和显示可视化效果)和后端部分(进行网络抓取和更新数据库)。
两个应用程序的骨架(有两个是因为我把任务分成两个应用程序)如下。
后端应用程序(scraper
):
- 连接到数据库
- 如果表不存在则创建表
- 数据抓取器
- 在数据库上保存数据
- 与数据库断开连接。
这个后端应用程序每年只能启动几次(为此,如果使用 Unix,我可以配置一个 CRON 文件)。
前端应用程序(viz
):
- 连接到数据库
- 启动一个正在等待端口 3000 的服务器(我需要它来进行可视化)
- 每次用户刷新页面 (
onLoad()
) 时,应用程序都会进行查询 (SELECT
),从数据库中获取数据。通过这种方式,数据总是更新的。
此应用程序仅由程序员启动一次(理想情况下)。
我创建了这种类型的文件夹结构(我使用了 npm init
和 Express
):
project
|_ scraper
|_ helpers // contains some useful .js files
|_ elaborateJson.js
|_ saveOnDb.js
|_ utilFunc.js
|_ node_modules // modules installed using `npm install moduleName --save`
|_ routes // contains the files that make scraping
|_ downloaderHome.js
|_ downloaderWork.js
|_ services // contains a files concerning the db
|_ postgreSQLlib.js
|_ app.js
|_ package.json
|_ package-lock.json
|_ viz
|_ helpers // // contains some useful .js files
|_ utilFunc.js
|_ node_modules // modules installed using `npm install moduleName --save`
|_ public // contains files for visualizations
|_ index.handlebars
|_ script.js
|_ style.css
|_ services // contains a file concerning the db
|_ postgreSQLlib.js
|_ app.js
|_ package.json
|_ package-lock.json
有了这个结构,我已经有两个不知道如何解决的问题:
1. postgreSQLlib.js
文件(以及 utilFunc.js
)在 scraper
和 viz
中是相同的。如何避免代码重复?
2. 我必须在 scraper
和 viz
文件夹中安装一些模块(例如 express-handlebars
和 express
)两次。
这是project/scraper/app.js
:
const downloaderHome = require('./routes/downloaderHome.js');
const downloaderWork = require('./routes/downloaderWork.js');
const postgreSQLlib = require('./services/postgreSQLlib.js');
const saveOnDb = require('./helpers/saveOnDb.js');
const utilFunc = require('./helpers/utilFunc.js');
const express = require('express');
const exphbs = require('express-handlebars');
var app = express();
start();
async function start() {
console.log('\n Connect to db');
await postgreSQLlib.connect();
console.log('\n Create tables if they do not exist');
await postgreSQLlib.createHomeTable();
await postgreSQLlib.createWorkTable();
console.log('\n Check if table \'home\' is updated or not');
if(!await utilFunc.isTableUpdated('home', 6418)) { // 6308
console.log('\n Download data for home');
await downloaderHome.download();
console.log('\n Saving data for home on db');
await saveOnDb.saveHome();
}
console.log('\n Check if table \'work\' is updated or not');
if(!await utilFunc.isTableUpdated('work', 6804)) {
console.log('\n Download data for work');
await downloaderWork.download();
console.log('\n Saving data for work on db');
await saveOnDb.saveWork();
}
console.log('\n Disconnect from db');
await postgreSQLlib.disconnect();
}
这是project/viz/app.js
:
const postgreSQLlib = require('./services/postgreSQLlib.js');
const utilFunc = require('./helpers/utilFunc.js');
const express = require('express');
const exphbs = require('express-handlebars');
const http = require('http');
var app = express();
var response;
var callback;
start();
async function start() {
console.log('\n Connect to db');
await postgreSQLlib.connect();
// how do I check when page is refreshed?!
http.get({
hostname: 'localhost',
port: 3000,
path: '/',
agent: false
}, callback);
callback = function(res) {
response = res;
console.log(response); // here response will return an object
console.log('refresh callback');
}
console.log(response);
console.log('refresh');
///////////////////////////////////////////////
// How do I check the disconnection from the db?
// If I disconnect now, the visualizations are no longer work.
// So when do I get disconnected?
// Create problems leaving the connection to the active db?
///////////////////////////////////////////////
//console.log('\n Disconnect from db');
//await postgreSQLlib.disconnect();
}
第一个应用程序 (project/scraper/app.js
) 运行完美。
第二次申请(project/viz/app.js
)号我希望你这样做:
- 连接到数据库[完成。有效]
- 启动一个正在等待端口 3000 的服务器(我需要它来进行可视化)[我该怎么做?往下看(*)]
- 每次用户刷新页面 (
onLoad()
) 时,应用程序都会进行查询 (SELECT
),从数据库中获取数据 [我该怎么做?]
(*) 我想到了这样的事情:
async function start() {
console.log('\n Connect to db');
await postgreSQLlib.connect();
console.log('\n Get data from db');
var dataHome = await postgreSQLlib.getTableHome();
var dataWork = await postgreSQLlib.getTableWork();
//console.log('\n Connect to my server');
pageLoad(dataHome, dataWork);
}
function pageLoad(dataHome, dataWork) {
var hbs = exphbs.create({
helpers: {
getDataHome: function() {
return JSON.stringify(dataHome);
},
getDataWork: function() {
return JSON.stringify(dataWork);
}
}
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.get('/', function(req, res, next) {
res.render('index', { // index is html filename
showTitle: true,
});
});
console.log('Go to http://localhost:3000/ to see visualizations');
app.listen(3000);
}
其中 dataHome
和 dataWork
是两个对象,它们包含使用 SELECT
查询从数据库下载的数据。
但是通过这种方式,数据只会被废弃一次,而不是每次用户刷新页面时都会被废弃。
帮助将不胜感激。谢谢!
编辑
你能说得更准确些吗?我尝试这样做,但它不起作用:
project/viz/app.js:
const postgreSQLlib = require('../shared_libs/postgreSQLlib.js');
const express = require('express');
var app = express();
start();
async function start() {
console.log('Connect to db');
await postgreSQLlib.connect();
app.get('/', fetchFreshData);
}
async function fetchFreshData(req, res) {
// download data from db
var dataHome = await postgreSQLlib.getTableHome();
var dataWork = await postgreSQLlib.getTableWork();
// fill this JSON using the results
var viewData = {dataHome, dataWork};
// pass data to view
res.render('index', viewData);
}
project\viz\view\index.车把:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Map</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head>
<body>
<div id='example'></div>
</body>
<script src='/script.js'></script>
</html>
project\viz\view\script.js:
console.log('viewData:', viewData);
我哪里错了?
编辑 2
好的,我再次修改viz/app.js
代码:
const postgreSQLlib = require('../shared_libs/postgreSQLlib.js');
const express = require('express');
const exphbs = require('express-handlebars');
var app = express();
start();
async function start() {
await postgreSQLlib.connect();
var hbs = Handlebars.registerHelper('json', function(context) {
return JSON.stringify(context);
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
app.get('/', fetchFreshData);
console.log('Go to http://localhost:3000/ to see data');
app.listen(3000);
}
async function fetchFreshData(req, res) {
// download data from db
var dataHome = await postgreSQLlib.getTableHome();
var dataWork = await postgreSQLlib.getTableWork();
// fill this JSON using the results
var viewData = {};
viewData.timestamp = Date.now();
viewData.entries = dataHome;
// pass data to view
res.render('index', viewData);
}
当我 运行 应用程序时,没有错误,但如果我连接到 http://localhost:3000/,浏览器会告诉我无法访问该站点。我觉得有点傻...
编辑 3
如果我对你的代码理解正确,那么你的代码中有一个(令人分心的)错误。
在 returnOBJ()
而不是 res.render('index', viewData);
中,它应该是 res.render('obj', viewData);
(与 obj.hbs
文件相关)。对吗?
我这样修改index.hbs文件:
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Index</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head>
<body>
<h1>INDEX<small>{{timestamp}}</small></h1>
</body>
<script>
// add global variables in the .hbs file
window.viewData_dataWork = {{ json entries }}
console.log(window.viewData);
</script>
<script src='/script.js'></script>
</html>
但我得到:
(node:207156) UnhandledPromiseRejectionWarning: Error: callback function required
at Function.engine (C:\...\node_modules\express\lib\application.js:295:11)
at start (C:\...\viz\app.js:20:6)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:182:7)
(node:207156) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:207156) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
这段代码我也看不懂
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express);
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
为什么用不同的值两次调用 app.set('view engine', ...)
?
编辑 4
我进一步简化了代码:
/viz/app.js:
const postgreSQLlib = require(__dirname + './../shared_libs/services/postgreSQLlib.js');
const express = require('express');
const hbs = require('hbs');
var app = express();
// Server initiator
async function start() {
await postgreSQLlib.connect();
// hbs
app.set('views', '' + __dirname + '/views');
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express);
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
});
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');
// router
app.get('/', testMe);
console.log('Go to http://localhost:3000/ to see data');
app.listen(3000);
}
// Your section with fresh data has been populated properly
async function testMe(req, res) {
console.log('testMe');
// fill this JSON using the results
var viewData = {};
viewData.data = 'this string';
// pass data to view
res.render('test', viewData);
}
// start the server
start();
/viz/views/test.hbs:
<html>
<head>
<title>Server test</title>
</head>
<body>
{{data}}
</body>
</html>
然后在提示命令中我转到 project/viz
并键入 node app.js
+ 回车。
进程启动并等待:没有错误。
当我转到 http://localhost:3000/
但我得到 Connection failed.
我快疯了
编辑 5
问题不是 connect
也不是构成 select 的函数,所以我稍微简化了代码。
现在,几乎可以正常工作了!
这是代码。
viz/app.js:
const postgreSQLlib = require(__dirname + './../shared_libs/services/postgreSQLlib.js');
const express = require('express');
var app = express()
const hbs = require('hbs');
const webapp_opts = {"port":3000};
Initialize();
//.: Setup & Start Server
async function Initialize(){
await postgreSQLlib.connect();
console.log("[~] starting ...")
//:[HBS]:Setup
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express)
app.set('views', "" + __dirname + "/views")
//:[HBS]:Helpers
hbs.registerHelper('json', function(context) {
return JSON.stringify(context);
})
//:[EXPRESS]:Router.Paths
app.get("/", IndexPathFunction);
// app.get("/script.js", scriptFile); <-- for script.js file
//:[EXPRESS]:Start
app.listen(webapp_opts.port,()=>{
console.log("[i] ready & listening","\n http://localhost:"+webapp_opts.port+"/")
})
}
/*async function scriptFile(req, res) { <-- for script.js file
console.log('\nscriptFile');
var viewData = {};
viewData.number = 50;
console.log('viewData:', viewData);
res.render('script.js', viewData);
}*/
//.: Router Function : "/"
async function IndexPathFunction(req,res){
var viewData = {};
viewData.timestamp = Date.now();
viewData.exJson = [{color: 'red', year: '1955'}, {color: 'blue', year: '2000'}, {color: 'yellow', year: '2013'}];
viewData.exString = 'example of string';
console.log('viewData:', viewData);
res.render('index', viewData);
}
viz/views/index.hbs:
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Index</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head>
<body>
<h1>INDEX timestamp: <small>{{timestamp}}</small></h1>
</body>
<script>
viewData = {};
console.log('viewData:', viewData);
viewData.exJson = JSON.parse('{{ json exJson }}'.replace(/"/g, '"').replace(/</, ''));
viewData.timestamp = {{timestamp}}; // doesn't work
viewData.exString = {{ exString }}; // doesn't work
console.log('viewData.exJson:', viewData.exJson);
console.log('viewData.timestamp:', viewData.timestamp);
console.log('viewData.exString:', viewData.exString);
</script>
<!--<script src='/script.js'></script>-->
</html>
问题是获取的数据类型不是 json。例如,当我尝试打印时间戳和 exString 时,它会给我错误。为什么?
此外,我想稍微清理一下代码,将 javascript 部分放入 script.js
文件中,该文件由 index.hbs
使用 <script src='/script.js'></script>
调用。
编辑 6
我发现 this tutorial 这对我很有用。
我通过添加一个css文件、一个图像和一个脚本来编辑index.hbs
文件(它只包含一个console.log('here');
但我的想法是放在script.js viewData
变量)。
项目/viz/views/index.hbs:
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Index</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<img src="/images/logo.png"/>
<h1>timestamp: <small>{{timestamp}}</small></h1>
<h2>Welcome in index.hbs</h2>
</body>
<script>
viewData = {};
console.log('viewData:', viewData);
viewData.exJson = JSON.parse('{{json exJson }}'.replace(/"/g, '"').replace(/</, ''));
viewData.timestamp = {{timestamp}};
viewData.exString = '{{exString}}';
console.log('viewData.exJson:', viewData.exJson);
console.log('viewData.timestamp:', viewData.timestamp);
console.log('viewData.exString:', viewData.exString);
</script>
<link href='/script/script.js' rel='script'>
</html>
我的文件结构是:
project
|_ node_modules
|_ scraper
|_ shared_libs
|_ viz
|_ app.js
|_ public
|_ css
|_ style.css
|_ images
|_ logo.png
|_ script
|_ script.js
|_ views
|_ index.hbs
现在我看到了图像并且使用了 css。但是脚本似乎不起作用,因为它没有在此处打印字符串。
我在互联网上搜索如何将变量从脚本标签传递到外部 js 文件,但我似乎没有找到适合我的东西。 我已经阅读了 handlebar API,但它们没有用。
共享(自定义)代码的解决方案:
With this structure I already have two problems that I don't know how to solve:
- the
postgreSQLlib.js
file (and alsoutilFunc.js
) is the same in both scraper and viz. How can I avoid this duplication of the code?
您已经在使用 require
,因此请从 :
project/scraper/services/postgreSQLlib.js
project/viz/services/postgreSQLlib.js
到项目根目录下新创建的目录
project
|_ shared_libs
|_ scraper
|_ viz
(在这个例子中我使用了shared_libs
,你可以随意命名)
project/shared_libs/postgreSQLlib.js
然后,来自代码:
const postgreSQLlib = require(__dirname+"/../shared_libs/postgreSQLlib.js");
这样您就不需要在 2 个位置的 2 个不同文件中维护代码
解决共享 node_modules :
同样适用于node_modules
简单地说,合并您当前的两个(node_modules 目录)
project/scraper/node_modules
project/viz/node_modules
进入 project
目录
project/node_modules
那么我的建议:
- 删除里面旧的(
project/scraper
&project/viz
) - 从现在开始,使用
project
目录中的npm i <module> --save
project/scraper
和 project/viz
都将使用 project/node_modules
无需复制整个库...
project/viz/app.js
的解决方案
如果你想在每个GET
请求上从数据库中获取数据
然后您必须在请求中包含 从 DB 中获取数据的逻辑:
app.get('/',FetchFreshData)
该函数将包含获取和构建 hbs 的 viewData,以使用 .hbs 标记中引用的新数据呈现
function FetchFreshData(req,res){
/* add your SELECT here */
var viewData = {} //fill this JSON using the results
res.render('index',viewData)
}
因此从逻辑上讲,每次您对“/”路由执行 GET
时,您将 运行 查询并接收包含新数据的“可视化”。
编辑:扩展答案
首先,我建议您更深入地了解车把 (hbs)。
如果您想在脚本中使用数据,您必须在服务器端注册一个帮助程序来根据需要呈现数据。
Handlebars.registerHelper('json',function(context){
return JSON.stringify(context)
})
我需要为你的情况做一个例子,假设 viewData 如下:
function FetchFreshData(req,res){
/* add your SELECT here */
var viewData = {} //fill this JSON using the results
viewData.timestamp = Date.now()
viewData.entries = dataHome
res.render('index',viewData)
}
会给我们(一个例子):
{
"timestamp":"1525182734",
"entries":[
{"name":"Entry 1"},
{"name":"Entry 2"},
{"name":"Entry 3"}
]
}
现在我将专注于您的模板 (.hbs) :
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>Map</title>
<script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
<link rel='stylesheet' type='text/css' href='/style.css' media='screen'/>
</head><body>
<h1>MAP<small>{{timestamp}}</small></h1>
<div id='example'>
{{#if entries}}
<ul>
{{#each entries}}
<li> this.name </li>
{{/each}}
</ul>
{{else}} No content... {{/if}}
</div>
</body>
<script>
//Add global variables in the .hbs file
window.viewData_entries = {{ json entries }}
</script>
<script src='/script.js'></script>
</html>
它将使用 JSON 对象(viewData)的“键” 访问数据并呈现视图...
编辑 2:尝试一些简单的东西
使用模块:hbs
并尝试使用我的 2 个简单示例,return 一个时间戳和一个对象,我还在某些部分修复了您的代码,以便您继续改进它更能满足您的需求。
const postgreSQLlib = require('../shared_libs/postgreSQLlib.js');
const express = require('express');
const hbs = require('hbs')
var app = express();
//:Server Initiator
async function start() {
await postgreSQLlib.connect();
//:HBS:
app.set('views', "" + __dirname + "/views");
app.set('view engine', 'hbs');
app.engine('hbs', hbs.__express);
hbs.registerHelper('json',function(context){return JSON.stringify(context)});
//:Router:
app.get('/', fetchFreshData);
app.get('/timestamp', returnTimestamp);
app.get('/obj', returnOBJ);
console.log('Go to http://localhost:3000/ to see data');
app.listen(3000);
}
//:Your section with fresh data has been populated properly
async function fetchFreshData(req, res) {
var viewData = {};
viewData.timestamp = Date.now();
viewData.dataWork = await postgreSQLlib.getTableWork();
viewData.dataHome = await postgreSQLlib.getTableHome();
// pass data to view
res.render('index', viewData);
}
/*
The index.hbs I posted earlier wont work anymore, since the "entries" key doesnt exist now... Try to understand the template engine with the following 2 examples below
*/
//:Simple Timestamp EXAMPLE
function returnTimestamp(req, res) {
var viewData = {};
viewData.timestamp = Date.now();
// pass data to view
res.render('timestamp_example', viewData);
}
/* This would be timestamp_example.hbs :
<html><head><title>Server Timestamp</title></head><body>{{timestamp}}</body></html>
*/
//:Simple JSON EXAMPLE
function returnOBJ(req, res) {
var viewData = {};
viewData.OBJ = {"key":"value"};
// pass data to view
res.render('json_example', viewData);
}
/* This would be json_example.hbs :
<html><head><title>Server Object</title></head><body>Page will alert "{{OBJ.key}}"<script>var OBJ = {{json OBJ}}; alert(OBJ.key);</script></body></html>
*/
//start the server :
start()
请记住,您需要为 /timestamp
和 /obj
路径添加新视图,我在每个服务器函数下方添加了一个注释示例。
----------
编辑 3:带回调的简单演示
const express = require('express'); var app = express()
const hbs = require('hbs')
const webapp_opts = {"port":3000}
//.: Setup & Start Server
function Initialize(){
console.log("[~] starting ...")
//:[HBS]:Setup
app.set('view engine', 'hbs'); app.engine('hbs', hbs.__express)
app.set('views', "" + __dirname + "/views")
//:[HBS]:Helpers
hbs.registerHelper('json',function(context){return JSON.stringify(context)})
//:[EXPRESS]:Router.Paths
app.get("/",IndexPathFunction)
app.get("/debug",(req,res)=>{
console.log("[GET]:/debug"); res.send("ok")
})
//:[EXPRESS]:Start
app.listen(webapp_opts.port,()=>{
console.log("[i] ready & listening","\n http://localhost:"+webapp_opts.port+"/")
})
}
//.: Router Function : "/"
function IndexPathFunction(req,res){
DBQuery((query_error,query_results)=>{
if(query_error){console.log("[!] DBQuery @ path : '/'\n",query_error.stack)}
else{
console.log("[+] DBResults :",query_results)
res.render("index",{data:query_results})
/*or*/// var viewData = {}; viewData.data=query_results; res.render("index",viewData)
}
})
}
//:[DB]:Example Query using callback method
function DBQuery(callback_function){
console.log("[>] DBQuery")
pool.connect((err,client)=>{
if (err) throw err
client.query('SELECT * FROM whatever',(err,res)=>{
client.release()
callback_function(err,res)
})
})
}
//------------------------------------
/* We can Initialize() the webapp
once we know the DB is accesible : */
const pg = require('pg')
const db_opts = {
"user":"dbuser","password":"secretpassword",
"host":"database.server.com", "port":3211,
"database":"mydb"
})
var pool = new pg.Pool(db_opts);
pool.on('error',(err,client)=>{
console.error('Unexpected error on idle client', err); process.exit(-1)
})
pool.connect((err,client,done)=>{
if(err){console.log("[!] DB Connection Error",err)}
else{console.log("[+] DB Connected"); client.release(); Initialize() }
})
请记住,了解问题所在的关键是控制流程并从小部件构建到大部件。 您应该阅读一下: https://node-postgres.com/ , specially this part if you want to use Express with async/await
无论如何,这个新编辑的部分应该为您提供一些关于制作最小工作版本的见解。 ;)