Node + Express 後臺開發 —— 起步
前面陸續學習了一下 node、npm、模組,也稍嘗試 Express,感覺得換一個思路加快進行。
比如筆者對前端的開發已較熟悉,如果領導給一個內部小網站
的需求,難道說你得給我配置一個後端
?
又不是做一個複雜的後端,只是簡單的資料儲存(增刪改查)、上傳檔案、下載csv或excel,無需考慮效能、穩定性、負載均衡等等,怎麼就不能做
目標
實現簡單後臺的開發和部署
Express 專案生成器
生成專案
可透過應用生成器工具 express-generator 可以快速建立一個應用的骨架
建立專案資料夾 spug-back-end,進入專案後執行 npx express-generator
:
Administrator@ /e/spug-back-end
$ npx express-generator
npm WARN exec The following package was not found and will be installed: express-generator@4.16.1
npm WARN deprecated mkdirp@0.5.1: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
warning: the default view engine will not be jade in future releases
warning: use `--view=jade' or `--help' for additional options
create : public\
create : public\javascripts\
create : public\images\
create : public\stylesheets\
create : public\stylesheets\style.css
create : routes\
create : routes\index.js
create : routes\users.js
create : views\
create : views\error.jade
create : views\index.jade
create : views\layout.jade
create : app.js
create : package.json
create : bin\
create : bin\www
install dependencies:
$ npm install
run the app:
$ DEBUG=spug-back-end:* npm start
生成如下內容:
Administrator@ /e/spug-back-end
$ ll
total 5
-rw-r--r-- 1 Administrator 197121 1075 Apr 14 15:06 app.js
drwxr-xr-x 1 Administrator 197121 0 Apr 14 15:06 bin/
-rw-r--r-- 1 Administrator 197121 301 Apr 14 15:06 package.json
drwxr-xr-x 1 Administrator 197121 0 Apr 14 15:06 public/
drwxr-xr-x 1 Administrator 197121 0 Apr 14 15:06 routes/
drwxr-xr-x 1 Administrator 197121 0 Apr 14 15:06 views/
Tip: 對於較老的 Node 版本(8.2.0 以下),請透過 npm 將 Express 應用程式生成器安裝到全域性環境中並使用
$ npm install -g express-generator
$ express
根據上文提示安裝依賴 npm install
:
Administrator@ /e/spug-back-end
$ npm install
npm WARN deprecated constantinople@3.0.2: Please update to at least constantinople 3.1.1
npm WARN deprecated transformers@2.1.0: Deprecated, use jstransformer
npm WARN deprecated jade@1.11.0: Jade has been renamed to pug, please install the latest version of pug instead of jade
added 99 packages, and audited 100 packages in 22s
1 package is looking for funding
run `npm fund` for details
8 vulnerabilities (1 low, 4 high, 3 critical)
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
全域性安裝 nodemon(在Node.js應用程式開發過程中使用的簡單監視器指令碼),編碼過程中無需重啟 node 服務即可生效:
PS E:\spug-back-end> npm i -g nodemon
changed 32 packages, and audited 33 packages in 1s
3 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
修改啟動指令碼:
"version": "0.0.0",
"private": true,
"scripts": {
- "start": "node ./bin/www"
+ "start": "nodemon ./bin/www"
透過 npm run start
本地啟動服務:
PS E:\spug-back-end> npm run start
> spug-back-end@0.0.0 start
> nodemon ./bin/www
[nodemon] 2.0.22
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./bin/www`
入口檔案 /bin/www.js
預設使用的是 3000 埠(var port = normalizePort(process.env.PORT || '3000');
)
瀏覽器訪問 http://localhost:3000/
,頁面顯示:
Express
Welcome to Express
專案解讀
模板
views
目錄中存放的是 jade 模板
檔案:
Administrator@ /e/spug-back-end/views (master)
$ ls
error.jade index.jade layout.jade
首頁
對應的模板是 index.jade:
Administrator@ /e/spug-back-end/views (master)
$ cat index.jade
extends layout
block content
h1= title
p Welcome to #{title}
Express 生成器預設使用 jade 模板,對前端不是很友好,筆者透過 npx express-generator --view=ejs
選用 ejs
模板(對前端更友好)重新建立。
首頁對應模板 index.ejs:
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>
</body>
</html>
入口檔案
bin/www.js
是應用的入口檔案。
核心程式碼如下:
// 載入 app.js
var app = require('../app');
var http = require('http');
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
// 建立 http server
var server = http.createServer(app);
server.listen(port);
完整程式碼如下:
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('spug-back-end:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
Tip: 和我們之前建立的最簡單的伺服器類似
app.js
入口檔案中引入 app.js,完整程式碼如下:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
// Creates an Express application
var app = express();
// view engine setup
// 模板引擎相關程式碼
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
// for parsing application/json
app.use(express.json());
// for parsing application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
// 放開靜態資源
app.use(express.static(path.join(__dirname, 'public')));
// 定義兩個路由。一個返回頁面一個返回後端資料
// app.use([path,] callback [, callback...]) - 在指定路徑掛載指定的一個或多箇中介軟體函式:當請求路徑的基匹配路徑時,執行中介軟體函式。
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
和我們之前用 Express 實現的報名系統 非常類似。這裡建立了一個 Express 應用,並定義了兩個示例路由:
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
// 返回頁面
app.use('/', indexRouter);
// 返回後端資料
app.use('/users', usersRouter);
路由
app.js 使用了兩個路由。內容如下:
// routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
// routes/users.js
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
module.exports = router;
瀏覽器訪問 http://localhost:3000/users
,頁面顯示 respond with a resource
。
mongodb
資料庫通常會安裝到 linux 中,這裡以 ubuntu 為例,透過 apt-get install mongodb
即可安裝,非常方便:
// 筆者剛已執行
root@linux:/home/pjl# apt-get install mongodb
Reading package lists... Done
Building dependency tree
Reading state information... Done
mongodb is already the newest version (1:3.6.9+really3.6.8+90~g8e540c0b6d-0ubuntu5.3).
0 upgraded, 0 newly installed, 0 to remove and 150 not upgraded.
資料庫現在已經啟動,我們透過mongo -version
驗證安裝成功:
root@linux:/home/pjl# mongo -version
MongoDB shell version v3.6.8
git version: 8e540c0b6db93ce994cc548f000900bdc740f80a
OpenSSL version: OpenSSL 1.1.1f 31 Mar 2020
allocator: tcmalloc
modules: none
build environment:
distarch: x86_64
target_arch: x86_64
mongodb 配置檔案是 /etc/mongodb.conf
。請注意下面兩項配置:
// 遠端連線
#bind_ip = 127.0.0.1
bind_ip = 0.0.0.0
// 開機自啟動
#auth = true
auth = true
MongoDB shell
輸入 mongo 即可進入 MongoDB shell
(操作mongo):
root@linux:/home/pjl# mongo
MongoDB shell version v3.6.8
connecting to: mongodb://127.0.0.1:27017
Implicit session: session { "id" : UUID("aedc6541-4a67-4e60-8eb4-d1325c82d061") }
MongoDB server version: 3.6.8
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
http://docs.mongodb.org/
Questions? Try the support group
http://groups.google.com/group/mongodb-user
Server has startup warnings:
2023-04-15T14:34:32.327+0800 I STORAGE [initandlisten]
2023-04-15T14:34:32.327+0800 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2023-04-15T14:34:32.327+0800 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
2023-04-15T14:34:34.861+0800 I CONTROL [initandlisten]
2023-04-15T14:34:34.861+0800 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
2023-04-15T14:34:34.861+0800 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
2023-04-15T14:34:34.861+0800 I CONTROL [initandlisten]
> help
db.help() help on db methods
db.mycoll.help() help on collection methods
sh.help() sharding helpers
rs.help() replica set helpers
help admin administrative help
help connect connecting to a db help
help keys key shortcuts
help misc misc things to know
help mr mapreduce
show dbs show database names
show collections show collections in current database
show users show users in current database
show profile show most recent system.profile entries with time >= 1ms
show logs show the accessible logger names
show log [name] prints out the last segment of log in memory, 'global' is default
use <db_name> set current database
db.foo.find() list objects in collection foo
db.foo.find( { a : 1 } ) list objects in foo where a == 1
it result of the last line evaluated; use to further iterate
DBQuery.shellBatchSize = x set default number of items to display on shell
exit quit the mongo shell
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
透過 help
可以檢視幫助,透過show dbs
發現現在有三個資料庫。
新建資料庫
透過 use 可以建立或切換資料庫。比如:
// use - 建立資料庫 pjl_db。如果存在則直接切換
> use pjl_db
switched to db pjl_db
// db - 目前操作的是 pjl_db 資料庫
> db
pjl_db
由於新建的資料庫 pjl_db 什麼都沒有,所以透過 show dbs
顯示不出來,可透過 createCollection 建立表,再次查詢即可顯示該資料庫。比如
// 新建資料庫未能展示
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
// 透過 tab 建有提示
> db.create
db.createCollection( db.createRole( db.createUser( db.createView(
// 建立表 users。這裡叫集合
> db.createCollection("users")
{ "ok" : 1 }
// 再次查詢則可顯示新建資料庫 pjl_db
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
pjl_db 0.000GB
Tip:db.createCollection("pjl_db", {size: 1024*1024, capped: true, max: 1000})
- 建立 pjl_db 資料庫,同時這個資料庫最多 1M,記錄數只能有1000條,在多一條則把第一條給替代。db.createCollection("users")
則不作限制。
刪除資料庫
透過 db.dropDatabase() 即可刪除資料庫。比如:
> db.dropDatabase()
{ "dropped" : "pjl_db", "ok" : 1 }
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
新增、檢視和刪除表
透過 db.createCollection("users")
新建 users 表。比如:
// 建立表 users。這裡叫集合
> db.createCollection("users")
{ "ok" : 1 }
// 再次查詢則可顯示新建資料庫 pjl_db
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
pjl_db 0.000GB
- db.getCollectionNames - 檢視有哪些表
- db.tableName.drop - 刪除某張表
示例:
// 新建兩張表: table-b、table-c
> db.createCollection("table-b")
{ "ok" : 1 }
> db.createCollection("table-c")
{ "ok" : 1 }
// tab 提示
> db.getCollection
db.getCollection( db.getCollectionInfos( db.getCollectionNames(
// 有哪些表
> db.getCollectionNames()
[ "table-b", "table-c", "users" ]
// 刪除table-b表失敗
> db.table-b.drop()
2023-04-15T15:17:10.232+0800 E QUERY [thread1] ReferenceError: b is not defined :
@(shell):1:1
// 刪除table-b表成功
> db["table-b"].drop()
true
> db.getCollectionNames()
[ "table-c", "users" ]
表格-新增資料
透過 db.users.save
可以向 users 表中單條、批次插入資料。甚至欄位名不同,欄位數量不同也能插入。請看示例:
// 給 users 表插入一條資料 {"name": 'pjl', age: 18}
> db.users.save({"name": 'pjl', age: 18})
WriteResult({ "nInserted" : 1 })
// 查詢users表
> db.users.find()
{ "_id" : ObjectId("643a513d73f16a13ae248f42"), "name" : "pjl", "age" : 18 }
// 批次插入資料
> db.users.save([{"name": 'pjl2', age: 19}, {"name": 'pjl3', age: 20}])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
// 批次插入成功
> db.users.find()
{ "_id" : ObjectId("643a513d73f16a13ae248f42"), "name" : "pjl", "age" : 18 }
{ "_id" : ObjectId("643a51db73f16a13ae248f44"), "name" : "pjl2", "age" : 19 }
{ "_id" : ObjectId("643a51db73f16a13ae248f45"), "name" : "pjl3", "age" : 20 }
// 換欄位名和欄位長度,也能插入成功
> db.users.save({"name2": 'pjl', age2: 18, tel: 131111111})
WriteResult({ "nInserted" : 1 })
> db.users.find()
{ "_id" : ObjectId("643a513d73f16a13ae248f42"), "name" : "pjl", "age" : 18 }
{ "_id" : ObjectId("643a51db73f16a13ae248f44"), "name" : "pjl2", "age" : 19 }
{ "_id" : ObjectId("643a51db73f16a13ae248f45"), "name" : "pjl3", "age" : 20 }
{ "_id" : ObjectId("643a529a73f16a13ae248f46"), "name2" : "pjl", "age2" : 18, "tel" : 131111111 }
Tip:向 mongo 表中插入資料非常自由,什麼欄位、什麼型別都可以。
表格-刪除資料
透過 db.users.remove({age2:18})
可以刪除 age2=18 的資料,透過 db.users.remove({})
刪除 users 表中所有資料。請看示例:
// 目前有4條資料
> db.users.find()
{ "_id" : ObjectId("643a513d73f16a13ae248f42"), "name" : "pjl", "age" : 18 }
{ "_id" : ObjectId("643a51db73f16a13ae248f44"), "name" : "pjl2", "age" : 19 }
{ "_id" : ObjectId("643a51db73f16a13ae248f45"), "name" : "pjl3", "age" : 20 }
{ "_id" : ObjectId("643a529a73f16a13ae248f46"), "name2" : "pjl", "age2" : 18, "tel" : 131111111 }
// 可以刪除 age2=18 的資料
> db.users.remove({age2:18})
WriteResult({ "nRemoved" : 1 })
> db.users.find()
{ "_id" : ObjectId("643a513d73f16a13ae248f42"), "name" : "pjl", "age" : 18 }
{ "_id" : ObjectId("643a51db73f16a13ae248f44"), "name" : "pjl2", "age" : 19 }
{ "_id" : ObjectId("643a51db73f16a13ae248f45"), "name" : "pjl3", "age" : 20 }
// 刪除 users 表中所有資料是 `db.users.remove({})`。`db.users.remove()` 會報錯
> db.users.remove()
2023-04-16T09:10:55.859+0800 E QUERY [thread1] Error: remove needs a query :
DBCollection.prototype._parseRemove@src/mongo/shell/collection.js:357:1
DBCollection.prototype.remove@src/mongo/shell/collection.js:382:18
@(shell):1:1
// 刪除 users 表中所有資料
> db.users.remove({})
WriteResult({ "nRemoved" : 3 })
> db.users.find()
>
表格-修改資料
透過 db.users.update({name:'pjl2'}, {$set: {age: 20}})
修改 name=pjl2 的資料,將 age 改為 20。
Tip:直接 db.users.update({name:'pjl'}, {age: 19})
會替換整條資料。
\$inc
指增加,如果需要減去,則將數字改成負數。
示例如下:
> db.users.find()
{ "_id" : ObjectId("643b4d0cd21fdd4d6f0b0484"), "name" : "pjl", "age" : 18 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0486"), "name" : "pjl2", "age" : 19 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0487"), "name" : "pjl3", "age" : 20 }
{ "_id" : ObjectId("643b4d1fd21fdd4d6f0b0488"), "name2" : "pjl", "age2" : 18, "tel" : 131111111 }
// 修改 name=pjl 的資料,將 age改為19
> db.users.update({name:'pjl'}, {age: 19})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
// 替換了條資料
> db.users.find()
{ "_id" : ObjectId("643b4d0cd21fdd4d6f0b0484"), "age" : 19 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0486"), "name" : "pjl2", "age" : 19 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0487"), "name" : "pjl3", "age" : 20 }
{ "_id" : ObjectId("643b4d1fd21fdd4d6f0b0488"), "name2" : "pjl", "age2" : 18, "tel" : 131111111 }
// 透過 $set 成功更改 age 而不影響其他欄位
> db.users.update({name:'pjl2'}, {$set: {age: 20}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.find()
{ "_id" : ObjectId("643b4d0cd21fdd4d6f0b0484"), "age" : 19 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0486"), "name" : "pjl2", "age" : 20 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0487"), "name" : "pjl3", "age" : 20 }
{ "_id" : ObjectId("643b4d1fd21fdd4d6f0b0488"), "name2" : "pjl", "age2" : 18, "tel" : 131111111 }
// 給 age 增加1
> db.users.update({name:'pjl2'}, {$inc: {age: 1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.find()
{ "_id" : ObjectId("643b4d0cd21fdd4d6f0b0484"), "age" : 19 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0486"), "name" : "pjl2", "age" : 21 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0487"), "name" : "pjl3", "age" : 20 }
{ "_id" : ObjectId("643b4d1fd21fdd4d6f0b0488"), "name2" : "pjl", "age2" : 18, "tel" : 131111111 }
// 給 age 減去1
> db.users.update({name:'pjl2'}, {$inc: {age: -1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.find()
{ "_id" : ObjectId("643b4d0cd21fdd4d6f0b0484"), "age" : 19 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0486"), "name" : "pjl2", "age" : 20 }
{ "_id" : ObjectId("643b4d17d21fdd4d6f0b0487"), "name" : "pjl3", "age" : 20 }
{ "_id" : ObjectId("643b4d1fd21fdd4d6f0b0488"), "name2" : "pjl", "age2" : 18, "tel" : 131111111 }
表格-查詢資料
首先清空 users 表,並插入6條資料。
find
透過 db.users.find()
查詢所有資料:
> db.users.find()
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "name" : "peng", "age" : 19, "tel" : "0730-1231" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia", "age" : 29, "tel" : "0730-1232" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048c"), "name" : "li", "age" : 39, "tel" : "0730-1233" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048d"), "name" : "pengjia", "age" : 49, "tel" : "0730-1234" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048e"), "name" : "pengjiali", "age" : 59, "tel" : "0730-1235" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "name" : "jiali", "age" : 69, "tel" : "0730-1236" }
>
大於小於
- $gt - 大於
- $gte - 大於等於
- $lt - 小於
- $lte - 小於等於
// 查詢 age 大於 50 的資料
> db.users.find({age:{$gt: 50}})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048e"), "name" : "pengjiali", "age" : 59, "tel" : "0730-1235" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "name" : "jiali", "age" : 69, "tel" : "0730-1236" }
// 查詢 age 大於 69 的資料
> db.users.find({age:{$gt: 69}})
// 查詢 age 大於等於 69 的資料
> db.users.find({age:{$gte: 69}})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "name" : "jiali", "age" : 69, "tel" : "0730-1236" }
// age 小於 20
> db.users.find({age:{$lt: 20}})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "name" : "peng", "age" : 19, "tel" : "0730-1231" }
// 查詢 age 大於 10,小於 40 的資料
> db.users.find({age:{$gt:10, $lt:40}})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "name" : "peng", "age" : 19, "tel" : "0730-1231" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia", "age" : 29, "tel" : "0730-1232" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048c"), "name" : "li", "age" : 39, "tel" : "0730-1233" }
>
單條件
db.users.find({name: "jia"})
- 查詢 name等於
jia 的資料db.users.find({name: /jia/})
- 查詢 name 中包含
jia 的資料db.users.find({name: /jia$/})
- 查詢 name 中以 jia結尾
的資料
// 查詢 name 等於 jia 的資料
> db.users.find({name: "jia"})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia", "age" : 29, "tel" : "0730-1232" }
// 查詢 name 中包含 jia 的資料
> db.users.find({name: /jia/})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia", "age" : 29, "tel" : "0730-1232" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048d"), "name" : "pengjia", "age" : 49, "tel" : "0730-1234" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048e"), "name" : "pengjiali", "age" : 59, "tel" : "0730-1235" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "name" : "jiali", "age" : 69, "tel" : "0730-1236" }
// 查詢 name 中以 jia 結尾的資料
> db.users.find({name: /jia$/})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia", "age" : 29, "tel" : "0730-1232" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048d"), "name" : "pengjia", "age" : 49, "tel" : "0730-1234" }
多條件
db.users.find({$or: [{age: 19}, {name: 'jia'}]})
查詢 age=19 或 name=jia
// 查詢 age=19 或 name=jia
> db.users.find({$or: [{age: 19}, {name: 'jia'}]})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "name" : "peng", "age" : 19, "tel" : "0730-1231" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia", "age" : 29, "tel" : "0730-1232" }
列過濾
db.users.find({}, {name: 1})
- 只顯示 name 這個欄位。預設主鍵回返回。1 是顯示,0指不顯示db.users.find({}, {name: 1, age: 1, _id: 0})
- 只要 name 和 age 欄位,主鍵不要
// 只顯示 name 這個欄位。預設主鍵回返回
> db.users.find({}, {name: 1})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "name" : "peng" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048c"), "name" : "li" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048d"), "name" : "pengjia" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048e"), "name" : "pengjiali" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "name" : "jiali" }
// 只顯示 name 這個欄位,主鍵也不要
> db.users.find({}, {name: 1, _id: 0})
{ "name" : "peng" }
{ "name" : "jia" }
{ "name" : "li" }
{ "name" : "pengjia" }
{ "name" : "pengjiali" }
{ "name" : "jiali" }
// 只要 name 和 age 欄位,主鍵不要
> db.users.find({}, {name: 1, age: 1, _id: 0})
{ "name" : "peng", "age" : 19 }
{ "name" : "jia", "age" : 29 }
{ "name" : "li", "age" : 39 }
{ "name" : "pengjia", "age" : 49 }
{ "name" : "pengjiali", "age" : 59 }
{ "name" : "jiali", "age" : 69 }
排序
透過 db.users.find().sort({age: -1})
執行 age 逆序,正序則為1。
// age 逆序
> db.users.find({}, {age:1}).sort({age: -1})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "age" : 69 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048e"), "age" : 59 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048d"), "age" : 49 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048c"), "age" : 39 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "age" : 29 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "age" : 19 }
// age 正序
> db.users.find({}, {age:1}).sort({age: 1})
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "age" : 19 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "age" : 29 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048c"), "age" : 39 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048d"), "age" : 49 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048e"), "age" : 59 }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "age" : 69 }
分頁
分頁可以透過 skip 和 limit實現。例如 db.users.find().skip(2).limit(2)
查詢第二頁。跳過2條,查詢2條。
> db.users.find()
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "name" : "peng", "age" : 19, "tel" : "0730-1231" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia", "age" : 29, "tel" : "0730-1232" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048c"), "name" : "li", "age" : 39, "tel" : "0730-1233" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048d"), "name" : "pengjia", "age" : 49, "tel" : "0730-1234" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048e"), "name" : "pengjiali", "age" : 59, "tel" : "0730-1235" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "name" : "jiali", "age" : 69, "tel" : "0730-1236" }
// 查詢前3條
> db.users.find().limit(3)
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "name" : "peng", "age" : 19, "tel" : "0730-1231" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia", "age" : 29, "tel" : "0730-1232" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048c"), "name" : "li", "age" : 39, "tel" : "0730-1233" }
// 第一頁。跳過0條,查詢2條
> db.users.find().skip(0).limit(2)
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"), "name" : "peng", "age" : 19, "tel" : "0730-1231" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048b"), "name" : "jia", "age" : 29, "tel" : "0730-1232" }
// 第二頁。跳過2條,查詢2條
> db.users.find().skip(2).limit(2)
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048c"), "name" : "li", "age" : 39, "tel" : "0730-1233" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048d"), "name" : "pengjia", "age" : 49, "tel" : "0730-1234" }
// 第三頁。跳過4條,查詢2條
> db.users.find().skip(4).limit(2)
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048e"), "name" : "pengjiali", "age" : 59, "tel" : "0730-1235" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "name" : "jiali", "age" : 69, "tel" : "0730-1236" }
// 先 age 逆序,在取2條
> db.users.find().sort({age: -1}).skip(0).limit(2)
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048f"), "name" : "jiali", "age" : 69, "tel" : "0730-1236" }
{ "_id" : ObjectId("643bb3dfd21fdd4d6f0b048e"), "name" : "pengjiali", "age" : 59, "tel" : "0730-1235" }
透過 count() 查詢記錄數,例如 db.users.find({name: 'jia'}).count()
// 總共 6 條
> db.users.find().count()
6
// 符合條件的共 1 條
> db.users.find({name: 'jia'}).count()
1
> db.users.find({name: /jia/}).count()
4
db.users.findOne()
- 查詢第一條
// 查詢第一條
> db.users.findOne()
{
"_id" : ObjectId("643bb3dfd21fdd4d6f0b048a"),
"name" : "peng",
"age" : 19,
"tel" : "0730-1231"
}
專案目錄劃分
程式那麼複雜,不可能全寫在一起,筆者做如下分層:
路由層
- 匹配路由,呼叫控制層控制層
- 取得前端請求資料,加工處理,比如呼叫資料庫(services層),在返回資料給前端服務層
- 引用建立的資料庫模型,對資料進行增刪改查模型層
- 建立資料庫模型(這裡是建立表)
以使用者
模組為例,在這4層中分別建立如下 js:
- services // 服務層
- UserService.js // User 模組的服務層
- routes // 路由
- UserRouter.js // User 模組的路由。比如登入、登入
- models // 模型層
- UserModel.js // User Model
- controllers // 控制層
- UserController.js
連線資料庫
前面我們運算元據庫是直接進入 mongo shell 操作:
// 建立並切換到資料庫 pjl_db
use pjl_db
// 建立 users 表(這裡叫集合)
db.createCollection("users")
現在我們需要透過 node 來運算元據庫。
啟動mongo服務
透過 mongod --dbpath=/var/lib/mongodb --bind_ip=0.0.0.0 --port=27017
啟動mongo服務(未設定資料庫密碼)。其中 0.0.0.0
使用者遠端連線,埠是 27017
。
如果需要後臺啟動(關閉終端服務也不會停止),需要指定日誌路徑,就像這樣:
root@linux:/home/pjl# mongod --dbpath=/var/lib/mongodb --fork --logpath=/var/log/mongodb/mongodb.log --bind_ip=0.0.0.0 --port=27017
about to fork child process, waiting until server is ready for connections.
forked process: 133354
child process started successfully, parent exiting
檢視 mongod 程式:
root@linux:/home/pjl# ps aux |grep mongod
root 133354 7.5 1.5 976348 62496 ? Sl 09:46 0:00 mongod --dbpath=/var/lib/mongodb --fork --logpath=/var/log/mongodb/mongodb.log --bind_ip=0.0.0.0 --port=27017
root 133383 0.0 0.0 12120 716 pts/0 S+ 09:47 0:00 grep --color=auto mongod
root@linux:/home/pjl#
Tip:還可以透過指定配置檔案啟動:
root@linux:/home/pjl# mongod -f /etc/mongodb.conf
// 沒反應,透過 `tail -10 日誌` 能看到輸出
另起視窗進入 mongo shell,執行 show dbs
報錯如下:
// 筆者將配置檔案中埠改為 27027
root@linux:/var/log/mongodb# mongo --port=27027
MongoDB shell version v3.6.8
connecting to: mongodb://127.0.0.1:27027/
Implicit session: session { "id" : UUID("dc184887-824d-474a-a942-3d42ff1a21bf") }
MongoDB server version: 3.6.8
> show dbs
2023-04-21T09:52:04.824+0800 E QUERY [thread1] Error: listDatabases failed:{
"ok" : 0,
"errmsg" : "there are no users authenticated",
"code" : 13,
"codeName" : "Unauthorized"
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:67:1
shellHelper.show@src/mongo/shell/utils.js:860:19
shellHelper@src/mongo/shell/utils.js:750:15
@(shellhelp2):1:1
>
網友說是 安裝mongo資料庫時,配置檔案中加了安全許可權的設定
,解決請看這裡
安裝 mongoose
透過 npm i mongoose -D
安裝 mongoose —— Mongoose is a MongoDB object modeling tool)。
透過 mongoose 運算元據庫非常方便
連線資料庫
連線資料庫很簡單,只需要先啟動資料庫服務,然後在啟動應用前連線資料庫即可。程式碼如下:
// bin/www.js
var http = require('http');
+ // 引入資料庫
+ require('../config/db.config')
var port = normalizePort(process.env.PORT || '3000');
// config/db.config.js
const mongoose = require("mongoose")
// mongo服務 ip
mongoose.connect("mongodb://192.168.1.223:27017/pjl_db")
mongoose.connection.once("open", () => {
console.log('資料庫連線成功')
})
透過 mongo shell 查到目前只有3個資料庫:
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
啟動應用,控制檯輸出 資料庫連線成功
:
PS E:\spug-back-end> npm run start
> spug-back-end@0.0.0 start
> nodemon ./bin/www
[nodemon] 2.0.22
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node ./bin/www`
資料庫連線成功
發現 pjl_db 資料庫自動被建立。
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
pjl_db 0.000GB
Tip:有了資料庫,還得需要表才能對資料進行增、刪、改、查。在 mongoose 中,需要建立一個 model(模型),可以把他當做一張表(或一條記錄),比如下文登入
模組的 UserModel.js
就是一個 model,向 model 插入一條資料時,mongoose 會自動建立一張名為 users 的表(或集合)。
登入
這裡我們完成系統登入模組的開發。
app.js
app.js 引入使用者路由。
// app.js
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
+ const UserRouter = require('./routes/UserRouter')
app.use('/users', usersRouter);
+ app.use(UserRouter);
UserRouter.js
定義登入路由(/user/login
),路由匹配成功,進入控制層處理。
// routes/UserRouter.js
// 使用者路由
var express = require('express');
var router = express.Router();
const UserController = require('../controllers/UserController.js')
/* POST users listing. */
router.post('/user/login', UserController.login);
module.exports = router;
UserController.js
控制層呼叫服務層,如果資料庫能透過使用者名稱和密碼查詢到該使用者則表明登入成功,否則返回使用者名稱密碼不匹配
。
// controllers/UserController.js
const UserService = require('../services/UserService')
const UserModel = require('../models/UserModel')
const UserController = {
login: async (req, res) => {
// req.body - 例如 {"username":"pjl","password":"123456"}
console.log('res.body', JSON.stringify(res.body))
var result = await UserService.login(req.body)
if(result.length === 0){
res.send({
code: '-1',
error: '使用者名稱密碼不匹配'
})
}else{
res.send({
code: '0',
error: ''
})
}
}
}
module.exports = UserController
UserService.js
模型層透過 model 查詢資料庫
// services/UserService.js
const UserModel = require('../models/UserModel.js')
const UserService = {
login: async ({username, password}) => {
return UserModel.find({
username,
password
})
}
}
module.exports = UserService
UserModel.js
mongoose 透過 model 建立表。
// models/UserModel.js
// model 與表一一對應
const mongoose = require('mongoose')
const Schema = mongoose.Schema
// 透過 schema 限制一下集合(表),否則什麼都能傳入,太自由了不好
const Usertype = {
username: String,
password: String,
// 性別
gender: Number,
// 頭像
avatar: String,
// 角色
role: Number, // 管理員1,編輯2
}
const UserModel = mongoose.model("user", new Schema(Usertype))
module.exports = UserModel
測試
這裡筆者在 git bash 中使用 curl
(客戶端的url) 模擬登入(post 使用者名稱+密碼):
Administrator@ ~/Desktop
$ curl -X POST -d 'username=pjl' -d 'password=123456' http://localhost:3000/user/login
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 76 100 48 100 28 1425 831 --:--:-- --:--:-- --:--:-- 2533
{"code":"-1","error":"使用者名稱密碼不匹配"}
由於 users 表中沒有資料,當然也就查詢不到(返回 {"code":"-1","error":"使用者名稱密碼不匹配"}
)。
筆者手動插入該條使用者資訊:
> use pjl_db
switched to db pjl_db
> db.users.save({username: 'pjl', password: '123456'})
WriteResult({ "nInserted" : 1 })
再次登入就能查詢到 {"code":"0","error":""}
:
Administrator@ ~/Desktop
$ curl -X POST -d 'username=pjl' -d 'password=123456' http://localhost:3000/user/login
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 51 100 23 100 28 1730 2106 --:--:-- --:--:-- --:--:-- 5100
{"code":"0","error":""}