PostgreSQL เป็นฐานข้อมูลที่ได้รับความนิยมอย่างมากในการพัฒนา Web Application โดยเฉพาะเมื่อใช้ร่วมกับ Node.js Workshop นี้จะพาคุณตั้งแต่ติดตั้ง PostgreSQL บน Cloud VPS สร้าง Database พัฒนา RESTful API ด้วย Express.js และเชื่อมต่อกับ PostgreSQL ผ่าน pg library
เนื้อหาเหมาะสำหรับนักพัฒนาที่ต้องการเรียนรู้การสร้าง Backend Application จริง ๆ บนเซิร์ฟเวอร์ตั้งแต่ต้นจนจบ พร้อมแนวทางปฏิบัติที่ดีสำหรับ Production
สิ่งที่ต้องเตรียม
- Cloud VPS ที่ใช้ Ubuntu 22.04 LTS พร้อม root access
- ความรู้พื้นฐาน JavaScript และ Node.js
- SSH client สำหรับเชื่อมต่อเข้า VPS
- RAM อย่างน้อย 1 GB
ขั้นตอนที่ 1 — ติดตั้ง PostgreSQL
# อัพเดตระบบ
sudo apt update && sudo apt upgrade -y
# ติดตั้ง PostgreSQL
sudo apt install postgresql postgresql-contrib -y
# ตรวจสอบสถานะ
sudo systemctl status postgresql
# ตรวจสอบเวอร์ชัน
psql --version
ขั้นตอนที่ 2 — สร้าง Database และ User
# เข้า PostgreSQL ด้วย user postgres
sudo -u postgres psql
-- สร้าง user สำหรับแอปพลิเคชัน
CREATE USER app_user WITH PASSWORD 'YourStrongPassword123!';
-- สร้าง database
CREATE DATABASE myapp_db OWNER app_user;
-- ให้สิทธิ์
GRANT ALL PRIVILEGES ON DATABASE myapp_db TO app_user;
-- ออก
\q
ตั้งค่า pg_hba.conf ให้รองรับ Password Authentication สำหรับ local connection:
# แก้ไข pg_hba.conf
sudo nano /etc/postgresql/16/main/pg_hba.conf
# เปลี่ยนบรรทัด:
# local all all peer
# เป็น:
# local all all md5
# Restart PostgreSQL
sudo systemctl restart postgresql
# ทดสอบ connection
psql -U app_user -d myapp_db -c "SELECT current_database(), current_user;"
ขั้นตอนที่ 3 — สร้าง Table และ Schema
# เข้า database
psql -U app_user -d myapp_db
-- สร้าง table สำหรับ users
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- สร้าง table สำหรับ posts
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT,
author_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
published BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- สร้าง index สำหรับ query ที่ใช้บ่อย
CREATE INDEX idx_posts_author ON posts(author_id);
CREATE INDEX idx_posts_published ON posts(published) WHERE published = true;
-- ตรวจสอบ table
\dt
\d users
\d posts
ขั้นตอนที่ 4 — ติดตั้ง Node.js
# ติดตั้ง Node.js ผ่าน NodeSource (LTS version)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install nodejs -y
# ตรวจสอบเวอร์ชัน
node --version
npm --version
# สร้างโฟลเดอร์โปรเจค
mkdir -p /var/www/myapp
cd /var/www/myapp
# สร้าง package.json
npm init -y
ขั้นตอนที่ 5 — ติดตั้ง Dependencies
# ติดตั้ง packages ที่ต้องการ
npm install express pg dotenv bcryptjs cors helmet
# ติดตั้ง dev dependencies
npm install --save-dev nodemon
# โครงสร้างโปรเจค:
# myapp/
# ├── .env
# ├── package.json
# ├── src/
# │ ├── index.js
# │ ├── config/
# │ │ └── database.js
# │ ├── routes/
# │ │ ├── users.js
# │ │ └── posts.js
# │ └── middleware/
# │ └── errorHandler.js
ขั้นตอนที่ 6 — ตั้งค่า Environment Variables
# สร้างไฟล์ .env
cat > .env << EOF
NODE_ENV=production
PORT=3000
# PostgreSQL Connection
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_db
DB_USER=app_user
DB_PASSWORD=YourStrongPassword123!
DB_MAX_CONNECTIONS=20
EOF
# ห้าม commit .env ขึ้น git
echo ".env" >> .gitignore
ขั้นตอนที่ 7 — สร้าง Database Connection Pool
Connection Pool เป็นสิ่งจำเป็นสำหรับ Production เพื่อจัดการ Connection อย่างมีประสิทธิภาพ
// src/config/database.js
const { Pool } = require('pg');
require('dotenv').config();
const pool = new Pool({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: parseInt(process.env.DB_MAX_CONNECTIONS) || 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Log connection events
pool.on('connect', () => {
console.log('Connected to PostgreSQL');
});
pool.on('error', (err) => {
console.error('PostgreSQL pool error:', err);
process.exit(-1);
});
module.exports = {
query: (text, params) => pool.query(text, params),
pool,
};
ขั้นตอนที่ 8 — สร้าง RESTful API
// src/index.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
require('dotenv').config();
const userRoutes = require('./routes/users');
const postRoutes = require('./routes/posts');
const errorHandler = require('./middleware/errorHandler');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
// Health check
app.get('/health', async (req, res) => {
const db = require('./config/database');
try {
const result = await db.query('SELECT NOW()');
res.json({
status: 'ok',
database: 'connected',
timestamp: result.rows[0].now,
});
} catch (err) {
res.status(500).json({ status: 'error', database: 'disconnected' });
}
});
// Routes
app.use('/api/users', userRoutes);
app.use('/api/posts', postRoutes);
// Error handling
app.use(errorHandler);
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
ขั้นตอนที่ 9 — สร้าง User Routes (CRUD)
// src/routes/users.js
const express = require('express');
const bcrypt = require('bcryptjs');
const db = require('../config/database');
const router = express.Router();
// GET /api/users — ดึง users ทั้งหมด
router.get('/', async (req, res, next) => {
try {
const { rows } = await db.query(
'SELECT id, username, email, created_at FROM users ORDER BY id'
);
res.json(rows);
} catch (err) {
next(err);
}
});
// GET /api/users/:id — ดึง user ตาม id
router.get('/:id', async (req, res, next) => {
try {
const { rows } = await db.query(
'SELECT id, username, email, created_at FROM users WHERE id = $1',
[req.params.id]
);
if (rows.length === 0) {
return res.status(404).json({ error: 'User not found' });
}
res.json(rows[0]);
} catch (err) {
next(err);
}
});
// POST /api/users — สร้าง user ใหม่
router.post('/', async (req, res, next) => {
try {
const { username, email, password } = req.body;
const password_hash = await bcrypt.hash(password, 10);
const { rows } = await db.query(
`INSERT INTO users (username, email, password_hash)
VALUES ($1, $2, $3)
RETURNING id, username, email, created_at`,
[username, email, password_hash]
);
res.status(201).json(rows[0]);
} catch (err) {
if (err.code === '23505') {
return res.status(409).json({ error: 'Username or email already exists' });
}
next(err);
}
});
// PUT /api/users/:id — อัพเดต user
router.put('/:id', async (req, res, next) => {
try {
const { username, email } = req.body;
const { rows } = await db.query(
`UPDATE users SET username = $1, email = $2, updated_at = NOW()
WHERE id = $3
RETURNING id, username, email, updated_at`,
[username, email, req.params.id]
);
if (rows.length === 0) {
return res.status(404).json({ error: 'User not found' });
}
res.json(rows[0]);
} catch (err) {
next(err);
}
});
// DELETE /api/users/:id — ลบ user
router.delete('/:id', async (req, res, next) => {
try {
const { rowCount } = await db.query(
'DELETE FROM users WHERE id = $1', [req.params.id]
);
if (rowCount === 0) {
return res.status(404).json({ error: 'User not found' });
}
res.json({ message: 'User deleted successfully' });
} catch (err) {
next(err);
}
});
module.exports = router;
ขั้นตอนที่ 10 — สร้าง Post Routes
// src/routes/posts.js
const express = require('express');
const db = require('../config/database');
const router = express.Router();
// GET /api/posts — ดึง posts พร้อม author info
router.get('/', async (req, res, next) => {
try {
const { page = 1, limit = 10 } = req.query;
const offset = (page - 1) * limit;
const { rows } = await db.query(
`SELECT p.id, p.title, p.content, p.published, p.created_at,
u.username AS author
FROM posts p
JOIN users u ON p.author_id = u.id
WHERE p.published = true
ORDER BY p.created_at DESC
LIMIT $1 OFFSET $2`,
[limit, offset]
);
const { rows: countRows } = await db.query(
'SELECT COUNT(*) FROM posts WHERE published = true'
);
res.json({
data: rows,
total: parseInt(countRows[0].count),
page: parseInt(page),
limit: parseInt(limit),
});
} catch (err) {
next(err);
}
});
// POST /api/posts — สร้าง post ใหม่
router.post('/', async (req, res, next) => {
try {
const { title, content, author_id, published } = req.body;
const { rows } = await db.query(
`INSERT INTO posts (title, content, author_id, published)
VALUES ($1, $2, $3, $4)
RETURNING *`,
[title, content, author_id, published || false]
);
res.status(201).json(rows[0]);
} catch (err) {
next(err);
}
});
module.exports = router;
ขั้นตอนที่ 11 — สร้าง Error Handler
// src/middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
console.error('Error:', err.message);
if (err.code === '23505') {
return res.status(409).json({ error: 'Duplicate entry' });
}
if (err.code === '23503') {
return res.status(400).json({ error: 'Referenced record not found' });
}
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message,
});
};
module.exports = errorHandler;
ขั้นตอนที่ 12 — ตั้งค่า PM2 สำหรับ Production
PM2 เป็น Process Manager สำหรับ Node.js ที่ช่วยให้แอปพลิเคชันทำงานต่อเนื่องและ restart อัตโนมัติเมื่อเกิดปัญหา
# ติดตั้ง PM2
sudo npm install -g pm2
# สร้าง ecosystem file
cat > ecosystem.config.js << 'EOF'
module.exports = {
apps: [{
name: 'myapp',
script: 'src/index.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
},
max_memory_restart: '256M',
error_file: '/var/log/myapp/error.log',
out_file: '/var/log/myapp/output.log',
}]
};
EOF
# สร้างโฟลเดอร์ log
sudo mkdir -p /var/log/myapp
sudo chown $USER:$USER /var/log/myapp
# เริ่มต้น app ด้วย PM2
pm2 start ecosystem.config.js
# ตรวจสอบสถานะ
pm2 status
pm2 logs myapp
# ตั้งค่า startup script (เริ่มอัตโนมัติเมื่อ reboot)
pm2 startup systemd
pm2 save
ขั้นตอนที่ 13 — ตั้งค่า Nginx Reverse Proxy
# สร้าง Nginx config
sudo nano /etc/nginx/sites-available/myapp
server {
listen 80;
server_name your_domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
# เปิดใช้งาน
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
ทดสอบ API
# ตรวจสอบ health check
curl http://localhost:3000/health
# สร้าง user
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"username":"john","email":"[email protected]","password":"secret123"}'
# ดึง users ทั้งหมด
curl http://localhost:3000/api/users
# สร้าง post
curl -X POST http://localhost:3000/api/posts \
-H "Content-Type: application/json" \
-d '{"title":"First Post","content":"Hello World!","author_id":1,"published":true}'
# ดึง posts
curl http://localhost:3000/api/posts
ปรับแต่งเพิ่มเติมสำหรับ Production
นอกจากการตั้งค่าพื้นฐานแล้ว ควรเพิ่มการปรับแต่งเหล่านี้สำหรับ Production:
# ปรับค่า PostgreSQL สำหรับ Node.js app
sudo nano /etc/postgresql/16/main/postgresql.conf
# ปรับค่าเหล่านี้:
# shared_buffers = 256MB
# work_mem = 4MB
# maintenance_work_mem = 128MB
# effective_cache_size = 768MB
# max_connections = 100
# Restart PostgreSQL
sudo systemctl restart postgresql
เพิ่ม Rate Limiting และ Input Validation:
# ติดตั้ง packages เพิ่มเติม
npm install express-rate-limit joi
// เพิ่มใน src/index.js
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 นาที
max: 100, // จำกัด 100 requests ต่อ IP
message: { error: 'Too many requests' },
});
app.use('/api/', limiter);
สรุป
Workshop นี้ครอบคลุมการสร้าง RESTful API ด้วย Node.js และ PostgreSQL ตั้งแต่การติดตั้งฐานข้อมูล การออกแบบ Schema การสร้าง CRUD API ด้วย Express.js การจัดการ Connection Pool การตั้งค่า PM2 สำหรับ Production ไปจนถึงการตั้งค่า Nginx Reverse Proxy
สำหรับขั้นตอนถัดไป ควรเพิ่ม Authentication ด้วย JWT, Input Validation ที่ละเอียดขึ้น, Unit Test และ Integration Test รวมถึง Migration Tool เช่น Knex.js หรือ Prisma เพื่อจัดการ Schema Change อย่างเป็นระบบ
แนะนำบริการ DE
การรัน Node.js Application พร้อม PostgreSQL ต้องการเซิร์ฟเวอร์ที่มี root access เพื่อติดตั้งและตั้งค่าทุกอย่างด้วยตนเอง Cloud VPS ของ DE เหมาะสำหรับงานประเภทนี้ รองรับ Node.js, PM2 และ PostgreSQL ได้อย่างเต็มประสิทธิภาพ พร้อม SSD Storage ที่ช่วยให้การ Query ฐานข้อมูลรวดเร็ว
หากคุณต้องการโฮสต์เว็บไซต์หรือ API ขนาดเล็กที่ไม่ต้องการจัดการเซิร์ฟเวอร์เอง Cloud Hosting ของ DE ก็เป็นทางเลือกที่สะดวก พร้อมระบบจัดการผ่าน Control Panel และ Backup อัตโนมัติ

