Cloudflare D1: ฐานข้อมูล SQL แบบ Serverless ฟรีสำหรับโปรเจกต์ขนาดเล็ก

Cloudflare D1

Cloudflare D1 — Photo by Joshua Sortino on Unsplash

Cloudflare D1: ฐานข้อมูล SQL แบบ Serverless ฟรีสำหรับโปรเจกต์ขนาดเล็ก

ฐานข้อมูลที่ทรงพลังโดยไม่ต้องจัดการเซิร์ฟเวอร์หรือจ่ายค่าบริการ


1. ทำความเข้าใจ Cloudflare D1

Cloudflare D1 คืออะไร?

  • นิยาม: D1 คือฐานข้อมูล SQL แบบ Serverless ที่พัฒนาโดย Cloudflare บนพื้นฐานของ SQLite
  • ความสามารถหลัก:
    • ฐานข้อมูล SQL ที่ไม่ต้องจัดการเซิร์ฟเวอร์
    • ทำงานที่ขอบของเครือข่าย (Edge) ใกล้กับผู้ใช้
    • รองรับคำสั่ง SQL มาตรฐาน
    • เชื่อมต่อกับ Cloudflare Workers ได้อย่างไร้รอยต่อ
  • ข้อดี: ฟรีสำหรับการใช้งานพื้นฐาน ไม่มีค่าใช้จ่ายซ่อนเร้น

ทำไมต้องใช้ Cloudflare D1?

  • ประหยัด: ไม่มีค่าใช้จ่ายสำหรับการใช้งานพื้นฐาน
  • ง่าย: ไม่ต้องจัดการเซิร์ฟเวอร์หรือการตั้งค่าที่ซับซ้อน
  • เร็ว: ทำงานที่ขอบของเครือข่าย ใกล้กับผู้ใช้
  • ปลอดภัย: จัดการโดย Cloudflare ซึ่งเป็นผู้นำด้านความปลอดภัย
  • ขยายได้: รองรับการเติบโตของแอปพลิเคชันโดยอัตโนมัติ

2. เปรียบเทียบ D1 กับฐานข้อมูลอื่น

D1 vs ฐานข้อมูล Serverless อื่นๆ

คุณสมบัติ Cloudflare D1 Firebase Firestore AWS DynamoDB PlanetScale
ประเภท SQL (SQLite) NoSQL NoSQL MySQL
ราคาเริ่มต้น ฟรี ฟรี (มีข้อจำกัด) ฟรี (มีข้อจำกัด) ฟรี (มีข้อจำกัด)
การเรียกใช้ 100,000 ครั้ง/วัน (ฟรี) 50,000 ครั้ง/วัน (ฟรี) 25 RCU/WCU (ฟรี) 1 GB storage, 5M rows read (ฟรี)
ความเข้ากันได้กับ SQL
การทำงานที่ Edge
การจัดการ ไม่ต้องจัดการ ไม่ต้องจัดการ ไม่ต้องจัดการ ไม่ต้องจัดการ
การเชื่อมต่อกับ Cloudflare ไร้รอยต่อ ต้องตั้งค่าเพิ่มเติม ต้องตั้งค่าเพิ่มเติม ต้องตั้งค่าเพิ่มเติม

D1 vs ฐานข้อมูล SQL แบบดั้งเดิม

คุณสมบัติ Cloudflare D1 MySQL PostgreSQL
ราคา ฟรี (แผนพื้นฐาน) ต้องจ่ายค่าเซิร์ฟเวอร์ ต้องจ่ายค่าเซิร์ฟเวอร์
การจัดการ ไม่ต้องจัดการ ต้องจัดการเอง ต้องจัดการเอง
การขยาย อัตโนมัติ ต้องทำเอง ต้องทำเอง
ความสามารถ SQL พื้นฐาน (SQLite) สมบูรณ์ สมบูรณ์
การทำงานที่ Edge
การสำรองข้อมูล อัตโนมัติ ต้องตั้งค่าเอง ต้องตั้งค่าเอง
การเชื่อมต่อ ผ่าน Workers ตรง ตรง

3. การเริ่มต้นใช้งาน Cloudflare D1

ขั้นตอนการสร้างฐานข้อมูลแรก

  1. สร้างบัญชี Cloudflare

    • ไปที่ cloudflare.com และสมัครบัญชีฟรี
    • ยืนยันอีเมลของคุณ
  2. ติดตั้ง Wrangler CLI

    • Wrangler คือเครื่องมือสำหรับพัฒนา Cloudflare Workers และ D1
    • ติดตั้งผ่าน npm:
    npm install -g wrangler
    
    • ล็อกอินเข้าสู่บัญชี Cloudflare:
    wrangler login
    
  3. สร้างฐานข้อมูล D1

    • ใช้คำสั่ง:
    wrangler d1 create <ชื่อฐานข้อมูล>
    # ตัวอย่าง
    wrangler d1 create my-database
    
    • บันทึก database_id ที่ได้รับ
  4. สร้างตาราง

    • สร้างไฟล์ schema.sql:
    CREATE TABLE users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE NOT NULL,
      created_at TEXT DEFAULT CURRENT_TIMESTAMP
    );
    
    • นำเข้า schema:
    wrangler d1 execute <ชื่อฐานข้อมูล> --file=./schema.sql
    
  5. ทดสอบฐานข้อมูล

    • เพิ่มข้อมูลทดสอบ:
    wrangler d1 execute <ชื่อฐานข้อมูล> --command="INSERT INTO users (name, email) VALUES ('Test User', '[email protected]')"
    
    • ดึงข้อมูล:
    wrangler d1 execute <ชื่อฐานข้อมูล> --command="SELECT * FROM users"
    

การเชื่อมต่อ D1 กับ Cloudflare Workers

  1. สร้างโปรเจกต์ Workers

    wrangler init my-d1-project
    cd my-d1-project
    
  2. ตั้งค่า D1 ใน wrangler.toml

    [[d1_databases]]
    binding = "DB" # ชื่อที่จะใช้อ้างอิงในโค้ด
    database_name = "<ชื่อฐานข้อมูล>"
    database_id = "<database_id>"
    
  3. เขียนโค้ด Worker ที่ใช้งาน D1

    // src/index.js
    export default {
      async fetch(request, env) {
        const { pathname } = new URL(request.url);
    
        // API สำหรับดึงข้อมูลผู้ใช้ทั้งหมด
        if (pathname === "/api/users" && request.method === "GET") {
          const { results } = await env.DB.prepare("SELECT * FROM users").all();
          return Response.json(results);
        }
    
        // API สำหรับเพิ่มผู้ใช้ใหม่
        if (pathname === "/api/users" && request.method === "POST") {
          const { name, email } = await request.json();
    
          if (!name || !email) {
            return new Response("Name and email are required", { status: 400 });
          }
    
          try {
            const stmt = env.DB.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
            const result = await stmt.bind(name, email).run();
            return Response.json({ success: true, id: result.lastRowId });
          } catch (error) {
            return new Response(`Error: ${error.message}`, { status: 500 });
          }
        }
    
        return new Response("Not found", { status: 404 });
      }
    };
    
  4. Deploy Worker

    wrangler deploy
    

4. การใช้งาน D1 ในสถานการณ์จริง

การสร้างระบบสมาชิก

  1. ออกแบบโครงสร้างฐานข้อมูล

    -- schema.sql
    CREATE TABLE users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      username TEXT UNIQUE NOT NULL,
      email TEXT UNIQUE NOT NULL,
      password_hash TEXT NOT NULL,
      created_at TEXT DEFAULT CURRENT_TIMESTAMP
    );
    
    CREATE TABLE sessions (
      id TEXT PRIMARY KEY,
      user_id INTEGER NOT NULL,
      expires_at TEXT NOT NULL,
      FOREIGN KEY (user_id) REFERENCES users(id)
    );
    
  2. สร้าง API สำหรับลงทะเบียน

    // ตัวอย่างโค้ดสำหรับการลงทะเบียน
    async function register(request, env) {
      const { username, email, password } = await request.json();
    
      // ตรวจสอบข้อมูล
      if (!username || !email || !password) {
        return new Response("Missing required fields", { status: 400 });
      }
    
      // ในสถานการณ์จริงควรเข้ารหัสรหัสผ่าน
      // นี่เป็นเพียงตัวอย่าง
      const password_hash = password; // ควรใช้ bcrypt หรือวิธีอื่นที่ปลอดภัย
    
      try {
        const stmt = env.DB.prepare(
          "INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)"
        );
        const result = await stmt.bind(username, email, password_hash).run();
    
        return Response.json({
          success: true,
          user_id: result.lastRowId
        });
      } catch (error) {
        return new Response(`Registration failed: ${error.message}`, { status: 500 });
      }
    }
    
  3. สร้าง API สำหรับเข้าสู่ระบบ

    // ตัวอย่างโค้ดสำหรับการเข้าสู่ระบบ
    async function login(request, env) {
      const { email, password } = await request.json();
    
      // ตรวจสอบข้อมูล
      if (!email || !password) {
        return new Response("Email and password are required", { status: 400 });
      }
    
      try {
        // ค้นหาผู้ใช้
        const user = await env.DB.prepare(
          "SELECT id, username, password_hash FROM users WHERE email = ?"
        ).bind(email).first();
    
        if (!user || user.password_hash !== password) {
          return new Response("Invalid email or password", { status: 401 });
        }
    
        // ตรวจสอบรหัสผ่านเสร็จสิ้น
    
        // สร้าง session
        const sessionId = crypto.randomUUID();
        const expiresAt = new Date();
        expiresAt.setDate(expiresAt.getDate() + 7); // หมดอายุใน 7 วัน
    
        await env.DB.prepare(
          "INSERT INTO sessions (id, user_id, expires_at) VALUES (?, ?, ?)"
        ).bind(sessionId, user.id, expiresAt.toISOString()).run();
    
        return Response.json({
          success: true,
          session_id: sessionId,
          user: {
            id: user.id,
            username: user.username
          }
        });
      } catch (error) {
        return new Response(`Login failed: ${error.message}`, { status: 500 });
      }
    }
    

การสร้างระบบบล็อกอย่างง่าย

  1. ออกแบบโครงสร้างฐานข้อมูล

    -- schema.sql
    CREATE TABLE posts (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      title TEXT NOT NULL,
      content TEXT NOT NULL,
      author_id INTEGER NOT NULL,
      published_at TEXT DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (author_id) REFERENCES users(id)
    );
    
    CREATE TABLE comments (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      post_id INTEGER NOT NULL,
      author_id INTEGER NOT NULL,
      content TEXT NOT NULL,
      created_at TEXT DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (post_id) REFERENCES posts(id),
      FOREIGN KEY (author_id) REFERENCES users(id)
    );
    
  2. สร้าง API สำหรับจัดการบทความ

    // ดึงบทความทั้งหมด
    async function getPosts(env) {
      const { results } = await env.DB.prepare(`
        SELECT p.id, p.title, p.published_at, u.username as author
        FROM posts p
        JOIN users u ON p.author_id = u.id
        ORDER BY p.published_at DESC
      `).all();
    
      return Response.json(results);
    }
    
    // ดึงบทความเดียว
    async function getPost(postId, env) {
      // ดึงข้อมูลบทความ
      const post = await env.DB.prepare(`
        SELECT p.id, p.title, p.content, p.published_at, u.username as author
        FROM posts p
        JOIN users u ON p.author_id = u.id
        WHERE p.id = ?
      `).bind(postId).first();
    
      if (!post) {
        return new Response("Post not found", { status: 404 });
      }
    
      // ดึงความคิดเห็น
      const { results: comments } = await env.DB.prepare(`
        SELECT c.id, c.content, c.created_at, u.username as author
        FROM comments c
        JOIN users u ON c.author_id = u.id
        WHERE c.post_id = ?
        ORDER BY c.created_at ASC
      `).bind(postId).all();
    
      // รวมข้อมูล
      post.comments = comments;
    
      return Response.json(post);
    }
    
    // สร้างบทความใหม่
    async function createPost(request, env) {
      const { title, content, author_id } = await request.json();
    
      if (!title || !content || !author_id) {
        return new Response("Missing required fields", { status: 400 });
      }
    
      try {
        const result = await env.DB.prepare(
          "INSERT INTO posts (title, content, author_id) VALUES (?, ?, ?)"
        ).bind(title, content, author_id).run();
    
        return Response.json({
          success: true,
          post_id: result.lastRowId
        });
      } catch (error) {
        return new Response(`Failed to create post: ${error.message}`, { status: 500 });
      }
    }
    

การสร้างระบบ E-commerce อย่างง่าย

  1. ออกแบบโครงสร้างฐานข้อมูล

    -- schema.sql
    CREATE TABLE products (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      description TEXT,
      price REAL NOT NULL,
      stock INTEGER NOT NULL DEFAULT 0,
      image_url TEXT,
      created_at TEXT DEFAULT CURRENT_TIMESTAMP
    );
    
    CREATE TABLE orders (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER NOT NULL,
      status TEXT NOT NULL DEFAULT 'pending',
      total REAL NOT NULL,
      created_at TEXT DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users(id)
    );
    
    CREATE TABLE order_items (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      order_id INTEGER NOT NULL,
      product_id INTEGER NOT NULL,
      quantity INTEGER NOT NULL,
      price REAL NOT NULL,
      FOREIGN KEY (order_id) REFERENCES orders(id),
      FOREIGN KEY (product_id) REFERENCES products(id)
    );
    
  2. สร้าง API สำหรับจัดการสินค้า

    // ดึงสินค้าทั้งหมด
    async function getProducts(env) {
      const { results } = await env.DB.prepare(
        "SELECT * FROM products WHERE stock > 0"
      ).all();
    
      return Response.json(results);
    }
    
    // ดึงข้อมูลสินค้าเดียว
    async function getProduct(productId, env) {
      const product = await env.DB.prepare(
        "SELECT * FROM products WHERE id = ?"
      ).bind(productId).first();
    
      if (!product) {
        return new Response("Product not found", { status: 404 });
      }
    
      return Response.json(product);
    }
    
  3. สร้าง API สำหรับการสั่งซื้อ

    // สร้างคำสั่งซื้อใหม่
    async function createOrder(request, env) {
      const { user_id, items } = await request.json();
    
      if (!user_id || !items || !items.length) {
        return new Response("Invalid order data", { status: 400 });
      }
    
      // เริ่ม transaction
      const db = env.DB;
    
      try {
        // คำนวณยอดรวม
        let total = 0;
        for (const item of items) {
          const product = await db.prepare(
            "SELECT price, stock FROM products WHERE id = ?"
          ).bind(item.product_id).first();
    
          if (!product) {
            return new Response(`Product ${item.product_id} not found`, { status: 404 });
          }
    
          if (product.stock < item.quantity) {
            return new Response(`Not enough stock for product ${item.product_id}`, { status: 400 });
          }
    
          total += product.price * item.quantity;
        }
    
        // สร้างคำสั่งซื้อ
        const orderResult = await db.prepare(
          "INSERT INTO orders (user_id, total) VALUES (?, ?)"
        ).bind(user_id, total).run();
    
        const order_id = orderResult.lastRowId;
    
        // เพิ่มรายการสินค้า
        for (const item of items) {
          const product = await db.prepare(
            "SELECT price FROM products WHERE id = ?"
          ).bind(item.product_id).first();
    
          // เพิ่มรายการในคำสั่งซื้อ
          await db.prepare(
            "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)"
          ).bind(order_id, item.product_id, item.quantity, product.price).run();
    
          // อัพเดทสต็อก
          await db.prepare(
            "UPDATE products SET stock = stock - ? WHERE id = ?"
          ).bind(item.quantity, item.product_id).run();
        }
    
        return Response.json({
          success: true,
          order_id,
          total
        });
      } catch (error) {
        return new Response(`Order creation failed: ${error.message}`, { status: 500 });
      }
    }
    

5. เทคนิคขั้นสูงในการใช้งาน D1

การใช้ Transactions

D1 รองรับ transactions ซึ่งช่วยให้คุณสามารถทำการเปลี่ยนแปลงหลายอย่างพร้อมกันได้อย่างปลอดภัย:

// ตัวอย่างการใช้ transaction
async function transferFunds(fromAccountId, toAccountId, amount, env) {
  // เริ่ม transaction
  const db = env.DB;

  try {
    // ตรวจสอบยอดเงิน
    const fromAccount = await db.prepare(
      "SELECT balance FROM accounts WHERE id = ?"
    ).bind(fromAccountId).first();

    if (!fromAccount || fromAccount.balance < amount) {
      return new Response("Insufficient funds", { status: 400 });
    }

    // หักเงินจากบัญชีต้นทาง
    await db.prepare(
      "UPDATE accounts SET balance = balance - ? WHERE id = ?"
    ).bind(amount, fromAccountId).run();

    // เพิ่มเงินในบัญชีปลายทาง
    await db.prepare(
      "UPDATE accounts SET balance = balance + ? WHERE id = ?"
    ).bind(amount, toAccountId).run();

    // บันทึกประวัติการโอน
    await db.prepare(
      "INSERT INTO transactions (from_account, to_account, amount) VALUES (?, ?, ?)"
    ).bind(fromAccountId, toAccountId, amount).run();

    return Response.json({ success: true });
  } catch (error) {
    // หากเกิดข้อผิดพลาด transaction จะถูกยกเลิกโดยอัตโนมัติ
    return new Response(`Transaction failed: ${error.message}`, { status: 500 });
  }
}

การใช้ Prepared Statements

การใช้ Prepared Statements ช่วยป้องกัน SQL Injection และเพิ่มประสิทธิภาพ:

// ตัวอย่างการใช้ Prepared Statements
async function searchUsers(query, env) {
  // ใช้ parameterized query เพื่อป้องกัน SQL Injection
  const searchTerm = `%${query}%`;

  const { results } = await env.DB.prepare(`
    SELECT id, username, email 
    FROM users 
    WHERE username LIKE ? OR email LIKE ?
  `).bind(searchTerm, searchTerm).all();

  return Response.json(results);
}

การจัดการข้อมูลขนาดใหญ่

เมื่อต้องการดึงข้อมูลจำนวนมาก ควรใช้การแบ่งหน้า (pagination):

// ตัวอย่างการทำ pagination
async function getProductsPaginated(page, pageSize, env) {
  const offset = (page - 1) * pageSize;

  // ดึงข้อมูลตามหน้า
  const { results: products } = await env.DB.prepare(`
    SELECT * FROM products
    ORDER BY created_at DESC
    LIMIT ? OFFSET ?
  `).bind(pageSize, offset).all();

  // ดึงจำนวนทั้งหมด
  const totalCount = await env.DB.prepare(
    "SELECT COUNT(*) as count FROM products"
  ).first();

  return Response.json({
    products,
    pagination: {
      page,
      pageSize,
      totalItems: totalCount.count,
      totalPages: Math.ceil(totalCount.count / pageSize)
    }
  });
}

6. การสำรองและนำเข้าข้อมูล

การสำรองข้อมูล (Backup)

D1 มีระบบสำรองข้อมูลอัตโนมัติ แต่คุณสามารถสำรองข้อมูลเองได้ด้วย Wrangler:

# สำรองฐานข้อมูลเป็นไฟล์ SQL
wrangler d1 export <ชื่อฐานข้อมูล> > backup.sql

การนำเข้าข้อมูล (Import)

คุณสามารถนำเข้าข้อมูลจากไฟล์ SQL ได้:

# นำเข้าข้อมูลจากไฟล์ SQL
wrangler d1 execute <ชื่อฐานข้อมูล> --file=./backup.sql

การโยกย้ายข้อมูลจากฐานข้อมูลอื่น

หากต้องการย้ายข้อมูลจากฐานข้อมูลอื่นมายัง D1:

  1. ส่งออกข้อมูลจากฐานข้อมูลเดิม

    • สำหรับ SQLite: sqlite3 old_database.db .dump > export.sql
    • สำหรับ MySQL: mysqldump -u username -p database_name > export.sql
    • สำหรับ PostgreSQL: pg_dump -U username database_name > export.sql
  2. ปรับแต่งไฟล์ SQL ให้เข้ากับ D1

    • D1 ใช้ SQLite เป็นพื้นฐาน ดังนั้นอาจต้องปรับแต่งคำสั่ง SQL ให้เข้ากับ SQLite
  3. นำเข้าข้อมูลสู่ D1

    wrangler d1 execute <ชื่อฐานข้อมูล> --file=./export.sql
    

7. การใช้ D1 ร่วมกับเฟรมเวิร์คยอดนิยม

การใช้ D1 กับ React

  1. สร้าง API ด้วย Cloudflare Workers

    // src/index.js (Worker)
    export default {
      async fetch(request, env) {
        // ตั้งค่า CORS
        const corsHeaders = {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
          "Access-Control-Allow-Headers": "Content-Type"
        };
    
        // จัดการ OPTIONS request (preflight)
        if (request.method === "OPTIONS") {
          return new Response(null, { headers: corsHeaders });
        }
    
        // เพิ่ม CORS headers ในทุก response
        const addCorsHeaders = (response) => {
          const newHeaders = new Headers(response.headers);
          Object.entries(corsHeaders).forEach(([key, value]) => {
            newHeaders.set(key, value);
          });
          return new Response(response.body, {
            status: response.status,
            statusText: response.statusText,
            headers: newHeaders
          });
        };
    
        // จัดการ API routes
        const url = new URL(request.url);
    
        try {
          if (url.pathname === "/api/products" && request.method === "GET") {
            const { results } = await env.DB.prepare("SELECT * FROM products").all();
            return addCorsHeaders(Response.json(results));
          }
    
          // เพิ่ม routes อื่นๆ ตามต้องการ
    
          return addCorsHeaders(new Response("Not found", { status: 404 }));
        } catch (error) {
          return addCorsHeaders(new Response(`Error: ${error.message}`, { status: 500 }));
        }
      }
    };
    
  2. เรียกใช้ API จาก React

    // React component
    import { useState, useEffect } from 'react';
    
    function ProductList() {
      const [products, setProducts] = useState([]);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
    
      useEffect(() => {
        async function fetchProducts() {
          try {
            const response = await fetch('https://your-worker.your-subdomain.workers.dev/api/products');
    
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
    
            const data = await response.json();
            setProducts(data);
          } catch (e) {
            setError(e.message);
          } finally {
            setLoading(false);
          }
        }
    
        fetchProducts();
      }, []);
    
      if (loading) return <p>Loading products...</p>;
      if (error) return <p>Error loading products: {error}</p>;
    
      return (
        <div>
          <h2>Products</h2>
          <ul>
            {products.map(product => (
              <li key={product.id}>
                <h3>{product.name}</h3>
                <p>{product.description}</p>
                <p>${product.price}</p>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    
    export default ProductList;
    

การใช้ D1 กับ Next.js

  1. สร้าง API Routes ใน Next.js

    // pages/api/products.js
    export default async function handler(req, res) {
      try {
        const response = await fetch('https://your-worker.your-subdomain.workers.dev/api/products');
    
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
    
        const data = await response.json();
        res.status(200).json(data);
      } catch (error) {
        res.status(500).json({ error: error.message });
      }
    }
    
  2. ใช้ SWR หรือ React Query เพื่อดึงข้อมูล

    // components/ProductList.js
    import useSWR from 'swr';
    
    const fetcher = (url) => fetch(url).then((res) => res.json());
    
    function ProductList() {
      const { data, error, isLoading } = useSWR('/api/products', fetcher);
    
      if (isLoading) return <p>Loading products...</p>;
      if (error) return <p>Error loading products</p>;
    
      return (
        <div>
          <h2>Products</h2>
          <ul>
            {data.map(product => (
              <li key={product.id}>
                <h3>{product.name}</h3>
                <p>{product.description}</p>
                <p>${product.price}</p>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    
    export default ProductList;
    

8. ข้อจำกัดและการแก้ไขปัญหา

ข้อจำกัดของ D1

  • ขนาดฐานข้อมูล: แผนฟรีมีขีดจำกัดขนาดฐานข้อมูล
  • จำนวนการเรียกใช้: 100,000 ครั้งต่อวันในแผนฟรี
  • ความสามารถ SQL: รองรับเฉพาะคำสั่ง SQLite ไม่รองรับฟีเจอร์ขั้นสูงของ MySQL หรือ PostgreSQL
  • การเชื่อมต่อ: เข้าถึงได้เฉพาะผ่าน Cloudflare Workers เท่านั้น
  • การทำงานแบบ Real-time: ไม่รองรับการทำงานแบบ real-time หรือ subscriptions

ปัญหาที่พบบ่อยและวิธีแก้ไข

  1. การเชื่อมต่อล้มเหลว

    • สาเหตุ: การตั้งค่า binding ไม่ถูกต้องใน wrangler.toml
    • วิธีแก้ไข: ตรวจสอบ database_id และ binding name ให้ถูกต้อง
  2. ข้อผิดพลาด SQL

    • สาเหตุ: คำสั่ง SQL ไม่ถูกต้องหรือไม่รองรับใน SQLite
    • วิธีแก้ไข: ตรวจสอบ syntax และปรับให้เข้ากับ SQLite
  3. ประสิทธิภาพช้า

    • สาเหตุ: ไม่มีการใช้ indexes หรือคำสั่ง SQL ไม่มีประสิทธิภาพ
    • วิธีแก้ไข: เพิ่ม indexes และปรับปรุงคำสั่ง SQL
  4. ข้อจำกัดการเรียกใช้

    • สาเหตุ: เกินขีดจำกัดการเรียกใช้ในแผนฟรี
    • วิธีแก้ไข: ใช้ caching หรือพิจารณาอัพเกรดแผน

การ Debug

  1. ใช้ Wrangler เพื่อทดสอบ SQL

    wrangler d1 execute <ชื่อฐานข้อมูล> --command="SELECT * FROM users LIMIT 10"
    
  2. ใช้ Local Development

    wrangler dev --local
    
  3. ตรวจสอบ Logs

    wrangler tail
    

9. กรณีศึกษา: การใช้งานจริงของ Startup

กรณีศึกษา 1: แอปพลิเคชันจัดการงาน

  • ความท้าทาย: Startup ต้องการสร้างแอปจัดการงานที่ประหยัดต้นทุนแต่มีประสิทธิภาพ
  • การใช้ D1:
    • สร้างฐานข้อมูลสำหรับเก็บข้อมูลผู้ใช้ โปรเจกต์ และงาน
    • ใช้ Cloudflare Workers เป็น API backend
    • ใช้ React เป็น frontend
  • ผลลัพธ์:
    • ประหยัดค่าใช้จ่ายได้ 100% เมื่อเทียบกับการใช้ฐานข้อมูลแบบดั้งเดิม
    • รองรับผู้ใช้ได้มากกว่า 1,000 คนโดยไม่มีปัญหาด้านประสิทธิภาพ
    • ลดเวลาในการบำรุงรักษาระบบลง 90%

กรณีศึกษา 2: เว็บไซต์ E-commerce ขนาดเล็ก

  • ความท้าทาย: ร้านค้าออนไลน์ขนาดเล็กต้องการระบบจัดการสินค้าและคำสั่งซื้อที่ประหยัด
  • การใช้ D1:
    • สร้างฐานข้อมูลสำหรับสินค้า คำสั่งซื้อ และลูกค้า
    • ใช้ Cloudflare Workers เป็น API
    • ใช้ Cloudflare Pages เป็น frontend
  • ผลลัพธ์:
    • ลดต้นทุนการดำเนินงานลง 80%
    • เว็บไซต์โหลดเร็วขึ้น 40% เนื่องจากฐานข้อมูลทำงานที่ Edge
    • รองรับช่วงการขายพิเศษที่มีทราฟฟิกสูงได้โดยไม่มีปัญหา

กรณีศึกษา 3: แพลตฟอร์มบล็อก

  • ความท้าทาย: นักเขียนต้องการแพลตฟอร์มบล็อกที่มีประสิทธิภาพแต่ไม่มีค่าใช้จ่าย
  • การใช้ D1:
    • สร้างฐานข้อมูลสำหรับบทความ ความคิดเห็น และผู้ใช้
    • ใช้ Markdown สำหรับเนื้อหาบทความ
    • ใช้ Cloudflare Workers และ Pages
  • ผลลัพธ์:
    • ประหยัดค่าใช้จ่ายได้ 100% เมื่อเทียบกับแพลตฟอร์มบล็อกแบบมีค่าใช้จ่าย
    • รองรับผู้อ่านได้มากกว่า 10,000 คนต่อวันโดยไม่มีปัญหา
    • ลดเวลาในการโหลดหน้าเว็บลง 60%

10. เมื่อไรควรอัพเกรดและทางเลือกอื่น

สัญญาณที่ควรพิจารณาอัพเกรด

  1. ปริมาณการใช้งาน:

    • จำนวนการเรียกใช้เกิน 100,000 ครั้งต่อวัน
    • ขนาดฐานข้อมูลเติบโตใกล้ขีดจำกัด
  2. ความต้องการเพิ่มเติม:

    • ต้องการฟีเจอร์ขั้นสูงที่ไม่มีใน SQLite
    • ต้องการการรองรับและ SLA ระดับองค์กร
  3. การเติบโตของธุรกิจ:

    • จำนวนผู้ใช้เพิ่มขึ้นอย่างรวดเร็ว
    • ต้องการความน่าเชื่อถือสูงขึ้น

ทางเลือกอื่นเมื่อเติบโตขึ้น

  1. Cloudflare D1 แผนพรีเมียม:

    • ยังคงใช้ D1 แต่อัพเกรดเป็นแผนพรีเมียม
    • ได้รับขีดจำกัดที่สูงขึ้นและการรองรับเพิ่มเติม
  2. ฐานข้อมูล SQL แบบจัดการ:

    • MySQL หรือ PostgreSQL บน AWS RDS, Google Cloud SQL
    • ได้รับความสามารถ SQL เต็มรูปแบบและการปรับแต่งมากขึ้น
  3. ฐานข้อมูล NoSQL:

    • MongoDB Atlas, Firebase Firestore
    • เหมาะสำหรับข้อมูลที่ไม่มีโครงสร้างแน่นอนและการขยายตัวแนวนอน
  4. ทางเลือกแบบ Serverless อื่นๆ:

    • PlanetScale (MySQL แบบ serverless)
    • Fauna (ฐานข้อมูล serverless แบบ global)
    • Supabase (PostgreSQL แบบจัดการ)

11. สรุป: ทำไม D1 ถึงเหมาะกับ Startup

  1. ประหยัดต้นทุน
    ฟรีสำหรับการใช้งานพื้นฐาน ไม่มีค่าใช้จ่ายซ่อนเร้น

  2. ไม่ต้องจัดการเซิร์ฟเวอร์
    ลดภาระในการดูแลระบบ ทีมสามารถโฟกัสที่การพัฒนาผลิตภัณฑ์

  3. ประสิทธิภาพสูง
    ทำงานที่ Edge ใกล้กับผู้ใช้ ทำให้แอปพลิเคชันตอบสนองเร็ว

  4. ใช้งานง่าย
    ใช้ SQL มาตรฐานที่นักพัฒนาคุ้นเคย ไม่ต้องเรียนรู้เทคโนโลยีใหม่

  5. เติบโตได้ตามต้องการ
    เริ่มต้นด้วยแผนฟรี และอัพเกรดเมื่อธุรกิจเติบโตขึ้น

“Cloudflare D1 เป็นทางเลือกที่ยอดเยี่ยมสำหรับ Startup ที่ต้องการฐานข้อมูลที่มีประสิทธิภาพโดยไม่ต้องกังวลเรื่องค่าใช้จ่ายหรือการจัดการระบบ ช่วยให้ทีมสามารถโฟกัสที่การสร้างผลิตภัณฑ์ที่ดีแทนที่จะเสียเวลากับการดูแลโครงสร้างพื้นฐาน”


12. แหล่งเรียนรู้เพิ่มเติม

เอกสารและบทความ:

คอมมูนิตี้:

คอร์สและวิดีโอ:

เคล็ดลับ: Cloudflare มีการอัพเดทฟีเจอร์ใหม่ๆ สำหรับ D1 อยู่เสมอ ติดตาม Cloudflare Blog และ Twitter เพื่อไม่พลาดฟีเจอร์ล่าสุดที่อาจช่วยให้การใช้งานฐานข้อมูลของคุณดียิ่งขึ้น