โค้ดดิ้งโปรแกรม

เริ่มต้นการทำ API ด้วย Express.js

ต่อไปนี้คือบทความแบบทำตามได้ทีละขั้นตอน สำหรับสร้าง API ด้วย Express.js + MySQL เก็บไฟล์ขึ้น AWS S3 ใช้ ULID เป็นไอดีหลัก และพร้อมใช้งานจริงได้

เป้าหมาย

  • สมัครพนักงานและล็อกอินพนักงาน
  • ล็อกอินแอดมิน + สร้างแอดมินใหม่
  • ข้อมูลบริษัทและแผนกให้เลือกตอนสมัคร
  • โปรไฟล์พนักงาน: ดูและแก้ไข
  • แอดมิน: ดูรายชื่อพนักงานทั้งหมด, ลบพนักงาน
  • อัปโหลดรูปโปรไฟล์ → resize ด้วย sharp → เก็บใน S3
  • JWT access/refresh + rate limit + validate inputs + Helmet + CORS
  • Sequelize + MySQL พร้อมรองรับ SSL ของ Azure
  • ใช้ ULID เป็นคีย์หลัก

1) แพ็กเกจที่ใช้ และใช้ทำอะไร

  • aws-sdk/client-s3 SDK V3 สำหรับเรียก S3
  • aws-sdk/lib-storage อัปโหลดแบบ multipart สำหรับไฟล์ใหญ่
  • bcryptjs แฮ็ชรหัสผ่าน
  • cors อนุญาต cross-origin จาก FRONTEND_URL
  • dotenv โหลดตัวแปรจาก .env
  • express-rate-limit จำกัดความพยายาม เช่น ล็อกอิน
  • express-validator ตรวจรูปแบบอินพุต
  • helmet เฮดเดอร์ความปลอดภัย
  • jsonwebtoken ออกและตรวจ JWT
  • multer รับไฟล์จาก multipart/form-data
  • mysql2 ไดรเวอร์ MySQL
  • sequelize ORM จัดการโมเดลและคำสั่ง SQL
  • ulid สร้างไอดีเรียงตามเวลา อ่านง่ายกว่า UUID
  • xlsx ส่งออกข้อมูลเป็นไฟล์ Excel ได้ภายหลัง
  • sharp แปลงและย่อภาพก่อนอัปโหลด S3

2) โครงสร้างโปรเจกต์

/certs/                     # เก็บไฟล์ CA ถ้าใช้ MySQL SSL (เช่น Azure G2)
  DigiCertGlobalRootG2.crt.pem
/config/
  db.js                     # ตั้งค่า Sequelize + SSL
/controllers/
  admin.controller.js
  auth.controller.js
  employee.controller.js
/middleware/
  auth.js                   # ตรวจ JWT + บทบาท
  rateLimit.js              # จำกัดความถี่
  validate.js               # wrapper express-validator
/migrations/                # ถ้าใช้ sequelize-cli
/models/
  index.js                  # รวมโมเดลและ associate
  company.model.js
  department.model.js
  employee.model.js
  admin.model.js
  refreshToken.model.js
/routes/
  admin.routes.js
  auth.routes.js
  employee.routes.js
/seeders/
  000-admin.seed.js
  001-company-dept.seed.js
/utils/
  jwt.js
  s3.js
  ulid.js
  passwords.js
  logger.js
  errors.js
index.js                    # บูทเซิร์ฟเวอร์

3) ตัวอย่าง .env ที่จำเป็น

NODE_ENV=development

DB_HOST=127.0.0.1
DB_NAME=appdb
DB_USERNAME=root
DB_PASSWORD=secret
DB_PORT=3306
DB_SSL=false
AZURE_MYSQL_SSL_CA=./certs/DigiCertGlobalRootG2.crt.pem
AZURE_MYSQL_RSA_CA= # ไม่ต้องใช้ถ้าไม่ได้บังคับ

AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=ap-southeast-1
AWS_ENDPOINT= 
AWS_S3_BUCKET=my-app-bucket
MAIN_FOLDER=uploads

FRONTEND_URL=http://localhost:5173
MAX_LOGIN_ATTEMPTS=5
LOGIN_ATTEMPT_TIMEOUT=5   # นาที

PORT=3000
TRUST_PROXY=1
SQL_LOG=0

JWT_ACCESS_SECRET=replace_me_access
JWT_REFRESH_SECRET=replace_me_refresh
JWT_ACCESS_TTL=15m
JWT_REFRESH_TTL=30d

จำเป็นจริง: DB_*, JWT_*, PORT, FRONTEND_URL, AWS_*, MAIN_FOLDER.
DB_SSL, AZURE_* ใช้เมื่อ MySQL บังคับ SSL เท่านั้น.

4) ติดตั้งและสคริปต์

npm init -y
npm i express cors helmet dotenv express-rate-limit express-validator jsonwebtoken bcryptjs multer sharp xlsx ulid
npm i sequelize mysql2
npm i @aws-sdk/client-s3 @aws-sdk/lib-storage
npm i -D nodemon

package.json scripts:

{
  "scripts": {
    "dev": "nodemon index.js",
    "start": "node index.js"
  }
}

5) ตั้งค่า DB และ Sequelize

const { Sequelize } = require('sequelize');
const fs = require('fs');
require('dotenv').config();

const sslEnabled = String(process.env.DB_SSL).toLowerCase() === 'true';
const dialectOptions = {};

if (sslEnabled) {
  const caPath = process.env.AZURE_MYSQL_SSL_CA;
  if (caPath) {
    dialectOptions.ssl = {
      require: true,
      ca: fs.readFileSync(caPath, 'utf8'),
    };
  } else {
    dialectOptions.ssl = { require: true, rejectUnauthorized: true };
  }
}

const sequelize = new Sequelize(
  process.env.DB_NAME,
  process.env.DB_USERNAME,
  process.env.DB_PASSWORD,
  {
    host: process.env.DB_HOST,
    port: Number(process.env.DB_PORT || 3306),
    dialect: 'mysql',
    logging: Number(process.env.SQL_LOG || 0) ? console.log : false,
    dialectOptions,
    define: { underscored: true, freezeTableName: true },
  }
);

module.exports = { sequelize };
const { sequelize } = require('../config/db');
const Company = require('./company.model');
const Department = require('./department.model');
const Employee = require('./employee.model');
const Admin = require('./admin.model');
const RefreshToken = require('./refreshToken.model');

// /models/index.js
Company.hasMany(Department, { foreignKey: 'company_id', onDelete: 'RESTRICT', onUpdate: 'CASCADE' });
Department.belongsTo(Company, { foreignKey: 'company_id' });

Company.hasMany(Employee,   { foreignKey: 'company_id', onDelete: 'RESTRICT', onUpdate: 'CASCADE' });
Department.hasMany(Employee,{ foreignKey: 'department_id', onDelete: 'RESTRICT', onUpdate: 'CASCADE' });
Employee.belongsTo(Company,   { foreignKey: 'company_id' });
Employee.belongsTo(Department,{ foreignKey: 'department_id' });


module.exports = {
  sequelize, Company, Department, Employee, Admin, RefreshToken
};
const { ulid } = require('ulid');
module.exports = { newId: () => ulid() };

โมเดลหลัก

const { DataTypes, Model } = require('sequelize');
const { sequelize } = require('../config/db');

class Company extends Model {}
Company.init({
  id: { type: DataTypes.STRING(26), primaryKey: true },
  name: { type: DataTypes.STRING(200), allowNull: false, unique: true },
}, { sequelize, modelName: 'company' });

module.exports = Company;
const { DataTypes, Model } = require('sequelize');
const { sequelize } = require('../config/db');

class Department extends Model {}
Department.init({
  id: { type: DataTypes.STRING(26), primaryKey: true },
  company_id: { type: DataTypes.STRING(26), allowNull: false },
  name: { type: DataTypes.STRING(200), allowNull: false },
}, { sequelize, modelName: 'department' });

module.exports = Department;
const { DataTypes, Model } = require('sequelize');
const { sequelize } = require('../config/db');

class Employee extends Model {}

Employee.init(
  {
    id: { type: DataTypes.STRING(26), primaryKey: true },
    employee_code: { type: DataTypes.STRING(32), unique: true, allowNull: false },
    first_name: { type: DataTypes.STRING(120), allowNull: false },
    last_name: { type: DataTypes.STRING(120), allowNull: false },

    company_id: {
      type: DataTypes.STRING(26),
      allowNull: false,
      references: { model: 'company', key: 'id' } // ชื่อตาราง
      // ถ้าอยากอ้างอิงด้วยคลาสโมเดล: references: { model: Company, key: 'id' }
    },

    department_id: {
      type: DataTypes.STRING(26),
      allowNull: false,
      references: { model: 'department', key: 'id' }
    },

    password_hash: { type: DataTypes.STRING(200), allowNull: false },
    avatar_url: { type: DataTypes.STRING(500) }
  },
  {
    sequelize,
    modelName: 'employee',
    tableName: 'employee' // ใส่เพื่อชัวร์ ถ้าไม่ได้ใช้ freezeTableName global
  }
);

module.exports = Employee;

const { DataTypes, Model } = require('sequelize');
const { sequelize } = require('../config/db');

class Admin extends Model {}
Admin.init({
  id: { type: DataTypes.STRING(26), primaryKey: true },
  email: { type: DataTypes.STRING(200), unique: true, allowNull: false },
  password_hash: { type: DataTypes.STRING(200), allowNull: false },
}, { sequelize, modelName: 'admin' });

module.exports = Admin;
const { DataTypes, Model } = require('sequelize');
const { sequelize } = require('../config/db');

class RefreshToken extends Model {}
RefreshToken.init({
  id: { type: DataTypes.STRING(26), primaryKey: true },
  user_id: { type: DataTypes.STRING(26), allowNull: false },
  user_role: { type: DataTypes.ENUM('employee','admin'), allowNull: false },
  token: { type: DataTypes.STRING(500), allowNull: false, unique: true },
  revoked: { type: DataTypes.BOOLEAN, defaultValue: false },
  expires_at: { type: DataTypes.DATE, allowNull: false },
}, { sequelize, modelName: 'refresh_token' });

module.exports = RefreshToken;

6) Utilities

const bcrypt = require('bcryptjs');
const SALT_ROUNDS = 12;

const hash = (plain) => bcrypt.hash(plain, SALT_ROUNDS);
const compare = (plain, hashv) => bcrypt.compare(plain, hashv);

module.exports = { hash, compare };
const jwt = require('jsonwebtoken');
require('dotenv').config();

const signAccess = (payload) =>
  jwt.sign(payload, process.env.JWT_ACCESS_SECRET, { expiresIn: process.env.JWT_ACCESS_TTL || '15m' });

const signRefresh = (payload) =>
  jwt.sign(payload, process.env.JWT_REFRESH_SECRET, { expiresIn: process.env.JWT_REFRESH_TTL || '30d' });

const verifyAccess = (token) =>
  jwt.verify(token, process.env.JWT_ACCESS_SECRET);

const verifyRefresh = (token) =>
  jwt.verify(token, process.env.JWT_REFRESH_SECRET);

module.exports = { signAccess, signRefresh, verifyAccess, verifyRefresh };
const { S3Client, HeadObjectCommand } = require('@aws-sdk/client-s3');
const { Upload } = require('@aws-sdk/lib-storage');
require('dotenv').config();

const s3 = new S3Client({
  region: process.env.AWS_REGION,
  endpoint: process.env.AWS_ENDPOINT || undefined,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
  },
  forcePathStyle: !!process.env.AWS_ENDPOINT // สำหรับ S3-compatible
});

async function putObject({ Key, Body, ContentType }) {
  const upload = new Upload({
    client: s3,
    params: { Bucket: process.env.AWS_S3_BUCKET, Key, Body, ContentType, ACL: 'private' }
  });
  await upload.done();
  return `s3://${process.env.AWS_S3_BUCKET}/${Key}`;
}

async function exists(Key) {
  try {
    await s3.send(new HeadObjectCommand({ Bucket: process.env.AWS_S3_BUCKET, Key }));
    return true;
  } catch {
    return false;
  }
}

module.exports = { putObject, exists };
const { validationResult } = require('express-validator');
module.exports = (req, res, next) => {
  const result = validationResult(req);
  if (result.isEmpty()) return next();
  return res.status(422).json({ errors: result.array() });
};
const { verifyAccess } = require('../utils/jwt');

function requireAuth(req, res, next) {
  const h = req.headers.authorization || '';
  const token = h.startsWith('Bearer ') ? h.slice(7) : null;
  if (!token) return res.status(401).json({ message: 'missing token' });
  try {
    const payload = verifyAccess(token);
    req.user = payload;
    return next();
  } catch {
    return res.status(401).json({ message: 'invalid token' });
  }
}

function requireRole(role) {
  return (req, res, next) => {
    if (!req.user || req.user.role !== role) return res.status(403).json({ message: 'forbidden' });
    next();
  };
}

module.exports = { requireAuth, requireRole };
const rateLimit = require('express-rate-limit');

// global limiter
const globalLimiter = rateLimit({
  windowMs: 60_000,
  max: 300,
  standardHeaders: true,
  legacyHeaders: false
});

// per-username login limiter (พื้นฐาน)
const attempts = new Map(); // key: userKey, value: { count, until }
function loginGuard(key, max, timeoutMin) {
  const now = Date.now();
  const rec = attempts.get(key);
  if (rec && rec.until > now) {
    if (rec.count >= max) return { blocked: true, until: rec.until };
  } else {
    attempts.delete(key);
  }
  return { blocked: false };
}
function recordFail(key, max, timeoutMin) {
  const now = Date.now();
  const rec = attempts.get(key) || { count: 0, until: now };
  rec.count += 1;
  if (rec.count >= max) rec.until = now + timeoutMin * 60_000;
  attempts.set(key, rec);
}
function resetAttempts(key) { attempts.delete(key); }

module.exports = { globalLimiter, loginGuard, recordFail, resetAttempts };

7) Controllers

const { newId } = require('../utils/ulid');
const { hash, compare } = require('../utils/passwords');
const { signAccess, signRefresh, verifyRefresh } = require('../utils/jwt');
const { Company, Department, Employee, Admin, RefreshToken } = require('../models');
const { Op } = require('sequelize');
const { addMinutes } = require('date-fns');
const { loginGuard, recordFail, resetAttempts } = require('../middleware/rateLimit');

const MAX = Number(process.env.MAX_LOGIN_ATTEMPTS || 5);
const TIMEOUT = Number(process.env.LOGIN_ATTEMPT_TIMEOUT || 5);

exports.registerEmployee = async (req, res) => {
  const { employee_code, first_name, last_name, company_id, department_id, password } = req.body;
  const empExists = await Employee.findOne({ where: { employee_code } });
  if (empExists) return res.status(409).json({ message: 'employee_code exists' });

  const ckCompany = await Company.findByPk(company_id);
  const ckDept = await Department.findOne({ where: { id: department_id, company_id } });
  if (!ckCompany || !ckDept) return res.status(400).json({ message: 'invalid company/department' });

  const password_hash = await hash(password);
  const emp = await Employee.create({
    id: newId(), employee_code, first_name, last_name, company_id, department_id, password_hash
  });

  return res.status(201).json({ id: emp.id });
};

exports.loginEmployee = async (req, res) => {
  const { employee_code, password } = req.body;
  const key = `emp:${employee_code}`;
  const check = loginGuard(key, MAX, TIMEOUT);
  if (check.blocked) return res.status(429).json({ message: 'too many attempts' });

  const emp = await Employee.findOne({ where: { employee_code } });
  if (!emp || !(await compare(password, emp.password_hash))) {
    recordFail(key, MAX, TIMEOUT);
    return res.status(400).json({ message: 'invalid credentials' });
  }
  resetAttempts(key);

  const access = signAccess({ sub: emp.id, role: 'employee' });
  const refresh = signRefresh({ sub: emp.id, role: 'employee' });
  await RefreshToken.create({
    id: newId(), user_id: emp.id, user_role: 'employee', token: refresh,
    expires_at: addMinutes(new Date(), 60 * 24 * 30)
  });

  res.json({ access, refresh });
};

exports.loginAdmin = async (req, res) => {
  const { email, password } = req.body;
  const key = `admin:${email}`;
  const check = loginGuard(key, MAX, TIMEOUT);
  if (check.blocked) return res.status(429).json({ message: 'too many attempts' });

  const admin = await Admin.findOne({ where: { email: { [Op.eq]: email } } });
  if (!admin || !(await compare(password, admin.password_hash))) {
    recordFail(key, MAX, TIMEOUT);
    return res.status(400).json({ message: 'invalid credentials' });
  }
  resetAttempts(key);

  const access = signAccess({ sub: admin.id, role: 'admin' });
  const refresh = signRefresh({ sub: admin.id, role: 'admin' });
  await RefreshToken.create({
    id: newId(), user_id: admin.id, user_role: 'admin', token: refresh,
    expires_at: addMinutes(new Date(), 60 * 24 * 30)
  });

  res.json({ access, refresh });
};

exports.createAdmin = async (req, res) => {
  const { email, password } = req.body;
  const exists = await Admin.findOne({ where: { email } });
  if (exists) return res.status(409).json({ message: 'email exists' });
  const admin = await Admin.create({
    id: newId(),
    email,
    password_hash: await hash(password)
  });
  res.status(201).json({ id: admin.id });
};
const { Employee } = require('../models');
const sharp = require('sharp');
const { putObject } = require('../utils/s3');

exports.getProfile = async (req, res) => {
  const me = await Employee.findByPk(req.user.sub, {
    attributes: ['id','employee_code','first_name','last_name','company_id','department_id','avatar_url']
  });
  if (!me) return res.status(404).json({ message: 'not found' });
  res.json(me);
};

exports.updateProfile = async (req, res) => {
  if (req.params.id !== req.user.sub && req.user.role !== 'admin') {
    return res.status(403).json({ message: 'forbidden' });
  }
  const emp = await Employee.findByPk(req.params.id);
  if (!emp) return res.status(404).json({ message: 'not found' });

  const { first_name, last_name } = req.body;
  if (typeof first_name === 'string') emp.first_name = first_name;
  if (typeof last_name === 'string') emp.last_name = last_name;

  if (req.file) {
    const buf = await sharp(req.file.buffer).resize(512, 512, { fit: 'cover' }).webp({ quality: 82 }).toBuffer();
    const key = `${process.env.MAIN_FOLDER}/avatars/${emp.id}.webp`;
    await putObject({ Key: key, Body: buf, ContentType: 'image/webp' });
    emp.avatar_url = key; // เก็บ key; ตอนเสิร์ฟให้ frontend ทำ pre-signed URL ฝั่งเซิร์ฟ
  }
  await emp.save();
  res.json({ ok: true });
};
const { Employee } = require('../models');

exports.listEmployees = async (req, res) => {
  const page = Math.max(1, Number(req.query.page || 1));
  const size = Math.min(100, Math.max(1, Number(req.query.size || 20)));
  const { rows, count } = await Employee.findAndCountAll({
    offset: (page - 1) * size, limit: size, order: [['created_at','DESC']],
    attributes: ['id','employee_code','first_name','last_name','company_id','department_id','avatar_url']
  });
  res.json({ data: rows, total: count, page, size });
};

exports.deleteEmployee = async (req, res) => {
  const id = req.params.id;
  const n = await Employee.destroy({ where: { id } });
  if (!n) return res.status(404).json({ message: 'not found' });
  res.json({ ok: true });
};

8) Routes + Validators + Multer

const router = require('express').Router();
const { body } = require('express-validator');
const validate = require('../middleware/validate');
const ctrl = require('../controllers/auth.controller');

router.post('/employee/register',
  body('employee_code').isLength({ min: 3 }),
  body('first_name').notEmpty(),
  body('last_name').notEmpty(),
  body('company_id').isLength({ min: 10 }),
  body('department_id').isLength({ min: 10 }),
  body('password').isStrongPassword({ minLength: 8, minSymbols: 0 }),
  validate,
  ctrl.registerEmployee
);

router.post('/employee/login',
  body('employee_code').notEmpty(),
  body('password').notEmpty(),
  validate,
  ctrl.loginEmployee
);

router.post('/admin/login',
  body('email').isEmail(),
  body('password').notEmpty(),
  validate,
  ctrl.loginAdmin
);

router.post('/admin/create',
  body('email').isEmail(),
  body('password').isStrongPassword({ minLength: 10, minSymbols: 0 }),
  validate,
  ctrl.createAdmin
);

module.exports = router;
const router = require('express').Router();
const { requireAuth, requireRole } = require('../middleware/auth');
const { body } = require('express-validator');
const validate = require('../middleware/validate');
const ctrl = require('../controllers/employee.controller');
const multer = require('multer');
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 5 * 1024 * 1024 } });

router.get('/employee/profile', requireAuth, requireRole('employee'), ctrl.getProfile);

router.patch('/employee/profile/edit/:id',
  requireAuth,
  upload.single('avatar'),
  body('first_name').optional().isLength({ min: 1 }),
  body('last_name').optional().isLength({ min: 1 }),
  validate,
  ctrl.updateProfile
);

module.exports = router;
const router = require('express').Router();
const { requireAuth, requireRole } = require('../middleware/auth');
const ctrl = require('../controllers/admin.controller');

router.get('/admin/employee/all', requireAuth, requireRole('admin'), ctrl.listEmployees);
router.delete('/admin/employee/delete/:id', requireAuth, requireRole('admin'), ctrl.deleteEmployee);

module.exports = router;

9) Seeders อย่างง่าย

const { Admin } = require('../models');
const { newId } = require('../utils/ulid');
const { hash } = require('../utils/passwords');

async function seedAdmin() {
  const email = 'root@example.com';
  const exists = await Admin.findOne({ where: { email } });
  if (!exists) {
    await Admin.create({ id: newId(), email, password_hash: await hash('ChangeMe1234') });
    console.log('seeded admin:', email);
  }
}
module.exports = { seedAdmin };
const { Company, Department } = require('../models');
const { newId } = require('../utils/ulid');

async function seedOrg() {
  const cid = newId();
  const did = newId();
  const c = await Company.findOne({ where: { name: 'Sample Co., Ltd.' } });
  if (!c) {
    await Company.create({ id: cid, name: 'Sample Co., Ltd.' });
    await Department.create({ id: did, company_id: cid, name: 'IT' });
    console.log('seeded company+department');
  }
}
module.exports = { seedOrg };

10) บูทเซิร์ฟเวอร์

require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const { globalLimiter } = require('./middleware/rateLimit');
const { sequelize } = require('./models');
const authRoutes = require('./routes/auth.routes');
const employeeRoutes = require('./routes/employee.routes');
const adminRoutes = require('./routes/admin.routes');

const app = express();

if (Number(process.env.TRUST_PROXY || 0)) app.set('trust proxy', true);

app.use(helmet());
app.use(cors({ origin: process.env.FRONTEND_URL, credentials: true }));
app.use(globalLimiter);
app.use(express.json({ limit: '2mb' }));

app.use('/api', authRoutes);
app.use('/api', employeeRoutes);
app.use('/api', adminRoutes);

app.get('/health', (req, res) => res.json({ ok: true }));

(async () => {
  await sequelize.sync(); // โปรดใช้ migrations ใน production
  // seed
  await require('./seeders/000-admin.seed').seedAdmin();
  await require('./seeders/001-company-dept.seed').seedOrg();

  const port = Number(process.env.PORT || 3000);
  app.listen(port, () => console.log(`API on :${port}`));
})();

11) ตัวอย่างการเรียกใช้งาน

curl -X POST http://localhost:3000/api/employee/register \
  -H "Content-Type: application/json" \
  -d '{"employee_code":"E1001","first_name":"Som","last_name":"chai","company_id":"<cid>","department_id":"<did>","password":"StrongPass123"}'
curl -X POST http://localhost:3000/api/employee/login \
  -H "Content-Type: application/json" \
  -d '{"employee_code":"E1001","password":"StrongPass123"}'
curl http://localhost:3000/api/employee/profile -H "Authorization: Bearer <access>"
curl -X PATCH http://localhost:3000/api/employee/profile/edit/<employee_id> \
  -H "Authorization: Bearer <access>" \
  -F "first_name=NewName" \
  -F "avatar=@./me.jpg"
curl -X POST http://localhost:3000/api/admin/login \
  -H "Content-Type: application/json" \
  -d '{"email":"root@example.com","password":"ChangeMe1234"}'
curl "http://localhost:3000/api/admin/employee/all?page=1&size=20" \
  -H "Authorization: Bearer <admin_access>"
curl -X DELETE http://localhost:3000/api/admin/employee/delete/<employee_id> \
  -H "Authorization: Bearer <admin_access>"

12) ความปลอดภัยที่ควรใช้จริง

  • ใช้ HTTPS เสมอ และตั้ง TRUST_PROXY=1 เมื่ออยู่หลัง reverse proxy
  • เปิด DB_SSL=true เมื่อใช้คลาวด์ DB ที่บังคับ SSL และใส่ CA ให้ถูกต้อง
  • เก็บไฟล์บน S3 แบบ private แล้วสร้าง pre-signed URL ตอนดาวน์โหลด แทนการเปิด public
  • แยก access และ refresh token, จัดเก็บ refresh ในฐานข้อมูลและทำ rotation เมื่อรีเฟรช
  • จำกัดขนาดไฟล์และชนิดไฟล์ใน multer และ sharp
  • เปิด helmet() ทั้งหมด และตั้ง CORS ชี้เฉพาะโดเมนหน้าเว็บ
  • บันทึก audit log เมื่อสร้าง/ลบพนักงาน
  • ใช้ sequelize-cli + migrations ใน production แทน sync()
  • สำรองฐานข้อมูลอย่างสม่ำเสมอ

13) เช็กลิสต์สั้น

  1. เติม .env ให้ครบ โดยเฉพาะ DB_*, AWS_*, JWT_*
  2. สร้างบัคเก็ต S3 และกำหนดสิทธิ์ IAM ให้เขียนได้
  3. ใส่ CA ไฟล์ใน /certs ถ้าใช้ Azure MySQL แบบ SSL
  4. npm run dev
  5. เรียก /health ดูสถานะ
  6. ใช้ seed แอดมินเริ่มต้น แล้วเปลี่ยนรหัสทันที

ต้องการเพิ่ม Excel export, refresh token rotation endpoint, หรือ pre-signed URL เสิร์ฟรูป แจ้งได้ ฉันจะเติมโค้ดให้ครบชุด.

Nidkoma

ชื่นชอบในการเขียนบทความ และการหาความรู้ในด้านต่างๆ ชอบถ่ายรูป ถ่ายวิดีโอ ชอบฟัง แต่ไม่ชอบพูดมั้ง ?? ^ ^

Related Articles

Back to top button