NodeJS, Express & Database

NodeJS ทำให้รัน JavaScript นอกเบราว์เซอร์ได้ — เขียน front-end และ back-end ด้วยภาษาเดียวกัน

NodeJS คืออะไร

Front-end React / HTML / CSS / JS npm install HTTP request (fetch/axios) JSON response Back-end Express บน NodeJS npm install express ทั้งสองฝั่งใช้ JavaScript และติดตั้ง package ด้วย npm เหมือนกัน

Express เป็น framework ที่ช่วยสร้าง route และจัดการ HTTP

npm & npx

npm init                 # สร้างโปรเจกต์ node ใหม่ (ได้ package.json)
npm install express      # ติดตั้ง express
npm install axios        # ติดตั้ง axios
npm install -g typescript# ติดตั้งแบบ global

สร้าง server ตัวแรก

const express = require('express');
const app = express();

app.get('/', (req, res) => {          // สร้าง route ที่ localhost/
    res.send('Hello World!');
});

app.listen(3000, () => {              // รัน server ที่ port 3000
    console.log('Example app listening at http://localhost:3000');
});

require vs import

require()import ... from
เป็นของNodeJS (CommonJS)ECMAScript module
ใช้กับmodule ที่จัดการระบบ เช่น fs, mariadbโมดูลทั่วไป เช่นในโค้ด React
ตัวอย่างconst fs = require('fs');import person from './person.js';
library ที่ทำงานกับระบบไฟล์หรือฐานข้อมูล (fs, mariadb) ใช้ได้เฉพาะฝั่ง server จึงไม่เห็นคำสั่ง require ในโค้ด React

fetch ฝั่ง server

app.get('/', async (req, res) => {
    try {
        const response = await fetch('https://jsonplaceholder.typicode.com/posts');
        const data = await response.json();
        res.json(data);                       // ส่งต่อให้ client
    } catch (error) {
        console.error(error);
        res.status(500).send('Internal Server Error');
    }
});

หรือใช้ axios ซึ่งกำหนด method ได้สะดวกกว่า:

const axios = require('axios');
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
const data = response.data;

ประเภทฐานข้อมูล

ประเภทลักษณะตัวอย่าง
Structured (relational)ตารางมี schema ชัดเจนMariaDB, MySQL, MS-SQL
Semi-structuredเก็บเป็น JSON / XMLMongoDB
Unstructuredไฟล์ดิบPDF, Word

CRUD กับ MariaDB

npm install express mariadb
const express = require('express');
const mariadb = require('mariadb');
const app = express();

const pool = mariadb.createPool({
    host: 'localhost',
    user: '<username>',
    password: '<password>',
    database: '<database>',
    connectionLimit: 5          // จำนวนการเชื่อมต่อสูงสุด
});

app.use(express.json());

Read — GET

app.get('/data', async (req, res) => {
    let conn;
    try {
        conn = await pool.getConnection();
        const rows = await conn.query("SELECT * FROM <table>");
        res.json(rows);
    } catch (err) {
        console.error(err);
        res.status(500).send('Internal Server Error');
    } finally {
        if (conn) conn.end();        // คืน connection กลับ pool เสมอ
    }
});

Create — POST

app.post('/data', async (req, res) => {
    let conn;
    try {
        conn = await pool.getConnection();
        const { field1, field2 } = req.body;
        await conn.query("INSERT INTO <table> (field1, field2) VALUES (?, ?)", [field1, field2]);
        res.send('Data inserted successfully');
    } catch (err) {
        res.status(500).send('Internal Server Error');
    } finally {
        if (conn) conn.end();
    }
});

Update — PUT / Delete — DELETE

app.put('/data/:id', async (req, res) => {
    const { field1, field2 } = req.body;
    const id = req.params.id;
    await conn.query("UPDATE <table> SET field1 = ?, field2 = ? WHERE id = ?", [field1, field2, id]);
});

app.delete('/data/:id', async (req, res) => {
    const id = req.params.id;
    await conn.query("DELETE FROM <table> WHERE id = ?", [id]);
});
สังเกตว่าใช้ placeholder ? แล้วส่งค่าเป็น array — นี่คือวิธีป้องกัน SQL Injection ห้ามต่อสตริงเอาค่าจากผู้ใช้ยัดเข้า query ตรง ๆ
กรณี JOIN แล้วชื่อ field ซ้ำกัน ให้ใส่ AS ตั้งชื่อใหม่ เช่น SELECT users.id AS id, orders.id AS order_id

MongoDB & mongoose

MongoDB > database > collection > document
MongoDBเทียบกับ relational
collectiontable
document (BSON)row
npm install express mongoose body-parser
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/<database>');

// ต้องสร้าง schema ก่อน (ODM — Object Document Mapping)
const itemSchema = new mongoose.Schema({
    name: String,
    description: String
});
const Item = mongoose.model('Item', itemSchema);

// CRUD
const items = await Item.find({});                              // อ่านทั้งหมด
const newItem = new Item(req.body); await newItem.save();       // เพิ่ม
await Item.findByIdAndUpdate(id, req.body, { new: true });      // แก้
await Item.findByIdAndDelete(id);                               // ลบ

ORM (Prisma)

ORM (Object Relational Mapping) = ออกแบบที่ model อย่างเดียว ไม่ต้องเขียน schema ในฐานข้อมูลเอง

npm install -g prisma
npx prisma init
npx prisma db push        # สร้าง/แก้ตารางตาม model
// schema.prisma
model User {
    id    Int    @id @default(autoincrement())
    name  String
    email String @unique
    posts Post[]
}
model Post {
    id       Int    @id @default(autoincrement())
    title    String
    authorId Int
    author   User   @relation(fields: [authorId], references: [id])
}
const users = await prisma.user.findMany();
const newUser = await prisma.user.create({ data: { name: 'John', email: 'john@example.com' } });
const updated = await prisma.user.update({ where: { id: 1 }, data: { name: 'Updated John' } });
const deleted = await prisma.user.delete({ where: { id: 1 } });
วิธี joinแนวคิด
include: { posts: true }Eager Loading — โหลด model ที่เกี่ยวข้องมาพร้อมกัน
select: { name: true, posts: true }Lazy Loading — เลือกโหลดเฉพาะที่ต้องการ
ข้อจำกัด: schema ของ Prisma ทำ extends สืบทอด object โดยตรงไม่ได้ — ต้องสร้าง model ใหม่แล้วแปลงเอง

Add-on: TypeScript

TypeScript (TS) = ภาษาที่ต่อยอดจาก JavaScript โดยเพิ่ม การกำหนดชนิดข้อมูล

ปัญหาของ JSTS แก้อย่างไร
Implicit conversion (แปลงชนิดอัตโนมัติ) ทำให้เกิด data type errorตรวจชนิดตั้งแต่ตอน compile
ขาดคุณสมบัติ OOP บางอย่าง เช่น interfaceมี interface และ generic <T>
npm install -g typescript
tsc -v                  # เช็คเวอร์ชัน
tsc app.ts              # compile เป็น app.js
tsc --init              # สร้าง tsconfig.json
function add(a: number, b: number) {
    return a + b;
}
console.log(add(true, 2));    // tsc app.ts → error ตั้งแต่ตอน compile

interface LabeledValue {
    label: string;
}
ค่าใน tsconfig.jsonความหมาย
rootDirโฟลเดอร์ต้นทางของโปรเจกต์ TS
outDirโฟลเดอร์ปลายทางที่ compile ออกมาเป็น JS (ค่าปริยาย ./dist/)
includeโฟลเดอร์ไหนบ้างที่จะ compile เช่น ["src"]
เบราว์เซอร์ไม่รู้จัก TypeScript — ต้อง compile เป็น JavaScript ก่อนเสมอ