Node.js 程式連線
在 Node.js 中,最常用的 PostgreSQL 驅動程式是 [`pg`](https://www.npmjs.com/package/pg)。基本流程如下:
1. 安裝 PostgreSQL 驅動程式
2. 建立資料庫連線
3. 執行 SQL
4. 讀取查詢結果
5. 關閉連線或釋放連線
---
## 1. 安裝 `pg`
在 Node.js 專案目錄執行:
```bash
npm install pg
```
如果使用 ES Module,可在 `package.json` 加上:
```json
{
"type": "module"
}
```
---
## 2. 建立 PostgreSQL 連線
### 方法一:使用單一連線
```js
const { Client } = require('pg');
const client = new Client({
host: 'localhost',
port: 5432,
user: 'postgres',
password: 'your_password',
database: 'mydb'
});
async function main() {
try {
await client.connect();
console.log('已連線到 PostgreSQL');
await client.end();
} catch (error) {
console.error('連線失敗:', error);
}
}
main();
```
如果使用 ES Module:
```js
import pg from 'pg';
const { Client } = pg;
const client = new Client({
host: 'localhost',
port: 5432,
user: 'postgres',
password: 'your_password',
database: 'mydb'
});
```
---
## 3. 使用連線池
在 Web API 或長時間執行的 Node.js 程式中,通常建議使用 `Pool`,它可以重複使用多個資料庫連線。
```js
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
port: 5432,
user: 'postgres',
password: 'your_password',
database: 'mydb',
max: 10
});
```
也可以使用連線字串:
```js
const pool = new Pool({
connectionString:
'postgresql://postgres:your_password@localhost:5432/mydb'
});
```
正式環境通常會把連線資訊放在環境變數中:
```js
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
```
例如 `.env`:
```env
DATABASE_URL=postgresql://postgres:your_password@localhost:5432/mydb
```
若要讀取 `.env`,可安裝:
```bash
npm install dotenv
```
程式中:
```js
require('dotenv').config();
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
```
---
# 4. 執行 SQL 指令
## 查詢資料:`SELECT`
假設資料表如下:
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(200) UNIQUE NOT NULL
);
```
Node.js 程式:
```js
async function getUsers() {
const result = await pool.query(
'SELECT id, name, email FROM users ORDER BY id'
);
console.log(result.rows);
}
getUsers()
.catch(console.error)
.finally(() => pool.end());
```
`result` 通常包含:
```js
{
rows: [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
],
rowCount: 2,
fields: [...]
}
```
最常使用的是:
```js
result.rows
```
以及:
```js
result.rowCount
```
---
## 5. 使用參數化查詢
不要直接把使用者輸入字串串接到 SQL 中:
```js
// 不建議,可能造成 SQL Injection
const name = req.query.name;
await pool.query(
`SELECT * FROM users WHERE name = '${name}'`
);
```
應使用 PostgreSQL 的參數化查詢:
```js
const name = 'Alice';
const result = await pool.query(
'SELECT id, name, email FROM users WHERE name = $1',
[name]
);
console.log(result.rows);
```
多個參數:
```js
const name = 'Alice';
const email = 'alice@example.com';
const result = await pool.query(
'SELECT * FROM users WHERE name = $1 AND email = $2',
[name, email]
);
```
`$1`、`$2` 會由 `pg` 安全地代入參數。
---
# 6. 新增資料:`INSERT`
```js
async function createUser(name, email) {
const result = await pool.query(
`INSERT INTO users (name, email)
VALUES ($1, $2)
RETURNING id, name, email`,
[name, email]
);
return result.rows[0];
}
createUser('Charlie', 'charlie@example.com')
.then(user => {
console.log('新增的使用者:', user);
})
.catch(console.error)
.finally(() => pool.end());
```
`RETURNING` 可以取得剛新增的資料:
```js
{
id: 3,
name: 'Charlie',
email: 'charlie@example.com'
}
```
---
# 7. 修改資料:`UPDATE`
```js
async function updateUser(id, name, email) {
const result = await pool.query(
`UPDATE users
SET name = $1, email = $2
WHERE id = $3
RETURNING id, name, email`,
[name, email, id]
);
return result.rows[0];
}
const user = await updateUser(
1,
'Alice Wang',
'alice.wang@example.com'
);
console.log(user);
```
如果找不到指定的 `id`,可能會得到:
```js
undefined
```
可以先檢查:
```js
if (result.rowCount === 0) {
console.log('找不到資料');
}
```
---
# 8. 刪除資料:`DELETE`
```js
async function deleteUser(id) {
const result = await pool.query(
'DELETE FROM users WHERE id = $1',
[id]
);
return result.rowCount;
}
const deletedCount = await deleteUser(1);
console.log(`刪除了 ${deletedCount} 筆資料`);
```
---
# 9. 完整範例
以下是一個可直接執行的 CommonJS 範例:
```js
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
port: 5432,
user: 'postgres',
password: 'your_password',
database: 'mydb'
});
async function main() {
try {
// 1. 新增資料
const insertResult = await pool.query(
`INSERT INTO users (name, email)
VALUES ($1, $2)
RETURNING *`,
['Alice', 'alice@example.com']
);
console.log('新增資料:', insertResult.rows[0]);
// 2. 查詢資料
const selectResult = await pool.query(
'SELECT * FROM users WHERE email = $1',
['alice@example.com']
);
console.log('查詢結果:', selectResult.rows);
// 3. 修改資料
const updateResult = await pool.query(
`UPDATE users
SET name = $1
WHERE email = $2
RETURNING *`,
['Alice Chen', 'alice@example.com']
);
console.log('修改結果:', updateResult.rows[0]);
// 4. 刪除資料
const deleteResult = await pool.query(
'DELETE FROM users WHERE email = $1',
['alice@example.com']
);
console.log('刪除筆數:', deleteResult.rowCount);
} catch (error) {
console.error('資料庫操作失敗:', error);
} finally {
await pool.end();
}
}
main();
```
---
# 10. 在 Web API 中取得資料
以 Express 為例:
```bash
npm install express pg
```
```js
const express = require('express');
const { Pool } = require('pg');
const app = express();
app.use(express.json());
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
app.get('/users', async (req, res) => {
try {
const result = await pool.query(
'SELECT id, name, email FROM users ORDER BY id'
);
res.json(result.rows);
} catch (error) {
console.error(error);
res.status(500).json({
error: '資料庫查詢失敗'
});
}
});
app.get('/users/:id', async (req, res) => {
try {
const result = await pool.query(
'SELECT id, name, email FROM users WHERE id = $1',
[req.params.id]
);
if (result.rowCount === 0) {
return res.status(404).json({
error: '找不到使用者'
});
}
res.json(result.rows[0]);
} catch (error) {
console.error(error);
res.status(500).json({
error: '資料庫查詢失敗'
});
}
});
app.listen(3000, () => {
console.log('伺服器執行於 http://localhost:3000');
});
```
啟動後:
```bash
node app.js
```
瀏覽:
```text
http://localhost:3000/users
```
可能得到:
```json
[
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}
]
```
---
# 11. 交易 Transaction
如果多個 SQL 必須全部成功,應使用交易:
```js
async function transferMoney(fromId, toId, amount) {
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query(
`UPDATE accounts
SET balance = balance - $1
WHERE id = $2`,
[amount, fromId]
);
await client.query(
`UPDATE accounts
SET balance = balance + $1
WHERE id = $2`,
[amount, toId]
);
await client.query('COMMIT');
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
```
使用連線池取得連線時,必須使用:
```js
client.release();
```
而不是:
```js
client.end();
```
`pool.end()` 通常只在程式即將結束時呼叫。
---
## 重點整理
```js
const result = await pool.query(
'SELECT * FROM users WHERE id = $1',
[userId]
);
const rows = result.rows;
```
常見操作:
| 操作 | SQL | Node.js 取得結果 |
|---|---|---|
| 查詢 | `SELECT` | `result.rows` |
| 新增 | `INSERT` | 搭配 `RETURNING` 取得新增資料 |
| 修改 | `UPDATE` | 搭配 `RETURNING` 取得修改後資料 |
| 刪除 | `DELETE` | `result.rowCount` 取得刪除筆數 |
實務上建議:
- 使用 `Pool` 管理連線
- 使用 `$1`、`$2` 參數化查詢
- 不要直接串接使用者輸入
- 連線密碼放在環境變數
- 非同步操作使用 `async/await`
- 交易中使用 `BEGIN`、`COMMIT`、`ROLLBACK`
- 使用完從連線池取得的連線後,要呼叫 `client.release()`
相關學習地圖、教學課程
Python 資料工程