اتصال Node.js به PostgreSQL
ارتباط بین Node.js و PostgreSQL یکی از رایجترین نیازها در توسعهٔ اپلیکیشنهای وب و سرویسهای بکاند است. در این مقاله بهصورت جامع روشهای متداول، نکات امنیتی، بهینهسازی و مثالهای عملی با کد قابل اجرا را بررسی میکنیم.
پیشنیازها و نصب
برای شروع به Node.js و یک پایگاه دادهٔ PostgreSQL نیاز دارید. معمولاً از بستهٔ رسمی pg استفاده میشود. نصب آن:
npm install pg dotenv
در اینجا از dotenv هم استفاده میکنیم تا اطلاعات حساس مثل آدرس دیتابیس و پسورد را در فایل محیطی ذخیره کنیم.
اتصال ساده و اجرای یک پرسوجو
// db.js
const { Client } = require('pg');
require('dotenv').config();
const client = new Client({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
});
async function test() {
await client.connect();
const res = await client.query('SELECT NOW()');
console.log(res.rows);
await client.end();
}
test().catch(err => console.error('DB Error', err));
این کد یک کلاینت ساده میسازد، به دیتابیس متصل میشود، زمان سرور را میگیرد و اتصال را میبندد. استفاده از dotenv و متغیر محیطی DATABASE_URL امنیت بیشتری فراهم میکند؛ همچنین در تولیدی با گزینهٔ ssl از اتصال امن استفاده میشود.
استفاده از Pool برای عملکرد بهتر
برای برنامههایی با چندین درخواست همزمان، ایجاد و بستن مکرر اتصال کارا نیست. بهجای آن از Pool استفاده کنید:
// pool.js
const { Pool } = require('pg');
require('dotenv').config();
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
module.exports = pool;
این کد یک مجموعه ارتباط (pool) با حداکثر 20 اتصال میسازد و آن را صادر میکند تا در اپلیکیشن از آن استفاده شود. پارامترها مثل idleTimeoutMillis و connectionTimeoutMillis را متناسب با بار اپلیکیشن تنظیم کنید.
پرسوجوهای پارامتری (Prepared Statements) برای جلوگیری از SQL Injection
// userRepository.js
const pool = require('./pool');
async function getUserByEmail(email) {
const text = 'SELECT id, email, name FROM users WHERE email = $1';
const values = [email];
const res = await pool.query(text, values);
return res.rows[0];
}
module.exports = { getUserByEmail };
در این قطعه از پارامترهای مکاندار $1 استفاده شده است. این روش از SQL Injection جلوگیری میکند و کارایی نیز افزایش مییابد زیرا پرسوجوها میتوانند آماده (prepared) شوند.
مثال تراکنش (Transaction) امن
// transactionExample.js
const pool = require('./pool');
async function transferFunds(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 (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}
module.exports = { transferFunds };
در این مثال از تراکنش برای انتقال وجه بین دو حساب استفاده شده است. در صورت بروز خطا تراکنش رولبک میشود تا یکپارچگی داده حفظ شود. همیشه در انتها اتصال را آزاد (release) کنید.
نکات امنیتی و پیکربندی
- اطلاعات حساس را در متغیرهای محیطی نگهداری کنید؛ از فایل .env در محیط توسعه و سرویسهای مدیریت پیکربندی در تولید استفاده کنید.
- برای محیط تولید از SSL/TLS استفاده کنید و
rejectUnauthorizedرا بسته به نیاز تنظیم کنید. - دسترسیهای دیتابیس را محدود کنید: کاربر دیتابیس را با حداقل مجوز لازم بسازید.
- از Prepared Statements و ORMهای ایمن بهره ببرید تا احتمال Injection کاهش یابد.
ORM یا دسترسی مستقیم؟ مقایسه کوتاه
| روش | مزایا | معایب |
|---|---|---|
| pg (دسترسى مستقیم) | فوقالعاده سبک، کنترل کامل، کارایی بالا | نوشتن کوئریها و مدیریت تراکنش نیاز به کدنویسی بیشتر دارد |
| Sequelize / TypeORM | مدلسازی، مایگریشن و امکانات آماده | سختتر برای کوئریهای پیچیده، سربار بیشتر |
| Prisma | تجربهٔ توسعه عالی، تایپسیف، مایگریشن ساده | سبب افزایش لایهٔ انتزاعی و محدودیت در کوئریهای خیلی پیچیده میشود |
بهینهسازی و نظارت (Performance & Monitoring)
- از ایندکسگذاری مناسب برای فیلدهایی که در WHERE یا JOIN زیاد استفاده میشوند بهره ببرید.
- لاگگیری کوئریهای کند را فعال کنید (pg_stat_statements) و کوئریهای سنگین را بهینه کنید.
- تنظیمات max connections در Postgres و اندازهٔ pool در اپلیکیشن را متناسب با منابع سرور قرار دهید.
- نسبت read/write را بررسی کنید و در صورت نیاز از replicaهای خواندنی استفاده کنید.
مثال کامل CRUD با Express و Pool
// app.js
const express = require('express');
const pool = require('./pool');
const app = express();
app.use(express.json());
app.post('/users', async (req, res) => {
try {
const { name, email } = req.body;
const result = await pool.query(
'INSERT INTO users(name, email) VALUES($1, $2) RETURNING id',
[name, email]
);
res.status(201).json({ id: result.rows[0].id });
} catch (err) {
console.error(err);
res.status(500).send('Server 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.rows.length === 0) return res.status(404).send('Not found');
res.json(result.rows[0]);
} catch (err) {
console.error(err);
res.status(500).send('Server error');
}
});
app.listen(3000, () => console.log('Server started on 3000'));
در این اپلیکیشن ساده از Pool استفاده شده و دو مسیر برای ایجاد و خواندن کاربر تعریف شده است. همهٔ کوئریها پارامتری هستند تا از تزریق SQL جلوگیری شود و خطاها بهصورت مناسب هندل میشوند.
جمعبندی و توصیههای نهایی
برای اتصال Node.js به PostgreSQL، بستهٔ pg گزینهای قدرتمند و انعطافپذیر است. برای اپلیکیشنهای کوچک استفاده از Pool و کوئریهای پارامتری کافی است. در سیستمهای بزرگتر از ابزارهای مانیتورینگ، replica برای خواندن، و در صورت تمایل از ORMهای مثل Prisma/Sequelize برای افزایش سرعت توسعه بهره ببرید. همیشه امنیت، مدیریت اتصال و بهینهسازی کوئریها را در اولویت قرار دهید.
در صورت نیاز میتوان مثالهای بیشتری (مهاجرت، إيمپورت داده، مانیتورینگ با pg_stat_statements) فراهم کرد.
آیا این مطلب برای شما مفید بود ؟




