Resumable Upload ด้วย tus Protocol: อัปโหลดไฟล์ใหญ่ เน็ตหลุดก็ไม่ต้องเริ่มใหม่

โดย CyberMAN


PHP · CODEIGNITER 4 · LARAVEL

Resumable Upload ด้วย tus Protocol: อัปโหลดไฟล์ใหญ่ เน็ตหลุดก็ไม่ต้องเริ่มใหม่

เคยอัปโหลดไฟล์ 2 GB ค้างอยู่ที่ 90% แล้วเน็ตหลุดไหมครับ? บทความนี้พาไปรู้จัก tus protocol และไลบรารี tus-php ที่ทำให้การอัปโหลดไฟล์ใหญ่ "หยุดแล้วต่อ" ได้จริง พร้อมตัวอย่างโค้ดสำหรับ CodeIgniter 4 และ Laravel

อัปโหลดสำเร็จแล้วหลุดกลางทาง แต่ resume ต่อจากจุดเดิมได้รอคิวอัปโหลด

01ทำไมการอัปโหลดไฟล์ใหญ่ถึง "พัง" ได้ง่าย

ลองนึกภาพผู้ใช้กำลังอัปโหลดวิดีโอขนาด 1.5 GB ผ่านฟอร์ม HTML ธรรมดา ใช้เวลาไปแล้วกว่าครึ่งชั่วโมง แถบโปรเกรสขยับมาถึง 92% แล้วจู่ ๆ Wi-Fi ก็หลุด หรือเบราว์เซอร์แครช ผลลัพธ์คือไฟล์ที่อัปโหลดไปทั้งหมดสูญเปล่า ผู้ใช้ต้องกดอัปโหลดใหม่ตั้งแต่ไบต์แรก

ปัญหานี้เกิดจากธรรมชาติของการอัปโหลดแบบ multipart/form-data ทั่วไป ซึ่งเซิร์ฟเวอร์มองไฟล์ทั้งก้อนเป็นคำขอเดียว ถ้าคำขอนั้นไม่จบสมบูรณ์ เซิร์ฟเวอร์จะไม่รู้เลยว่าไบต์ไหนมาถึงแล้วบ้าง และต้องทิ้งข้อมูลทั้งหมดไป ยิ่งผู้ใช้อยู่ในพื้นที่ที่สัญญาณอินเทอร์เน็ตไม่เสถียร เช่น ใช้เน็ตมือถือระหว่างเดินทาง ปัญหานี้ก็ยิ่งเกิดถี่ขึ้น

ทางออกที่หลายระบบใหญ่ ๆ อย่าง Vimeo หรือ Google Drive ใช้กันคือการอัปโหลดแบบ resumable นั่นคือแบ่งไฟล์เป็นชิ้นเล็ก ๆ (chunk) ส่งทีละชิ้น แล้วให้เซิร์ฟเวอร์จดจำว่าไบต์ล่าสุดที่ได้รับคือไบต์ที่เท่าไร เมื่อการเชื่อมต่อกลับมา ฝั่งไคลเอนต์ก็แค่ถามเซิร์ฟเวอร์ว่า "ตอนนี้ได้รับไปถึงไหนแล้ว" แล้วส่งต่อจากจุดนั้น ไม่ต้องเริ่มนับหนึ่งใหม่

02รู้จัก tus: มาตรฐานเปิดสำหรับ Resumable Upload

tus เป็นโปรโตคอลแบบเปิดที่ถูกออกแบบมาเฉพาะสำหรับการอัปโหลดไฟล์แบบ resumable บน HTTP จุดเด่นของ tus คือมันกำหนดมาตรฐานชัดเจนว่าไคลเอนต์กับเซิร์ฟเวอร์ควรคุยกันอย่างไร ทำให้นักพัฒนาไม่ต้องคิดค้น protocol ของตัวเองตั้งแต่ศูนย์ และมีไลบรารีรองรับในหลายภาษา ทั้งฝั่ง server (PHP, Node.js, Go) และฝั่ง client (JavaScript, iOS, Android)

สำหรับฝั่ง PHP มีไลบรารีชื่อ ankitpokhrel/tus-php ซึ่งเป็น implementation ของ tus server แบบ framework-agnostic หมายความว่าเอาไปต่อกับ CodeIgniter, Laravel หรือ PHP ล้วน ๆ ก็ได้ทั้งนั้น

03tus ทำงานอย่างไรเบื้องหลัง

หัวใจของ tus คือแนวคิดเรื่อง offset หรือ "ตำแหน่งไบต์ล่าสุดที่อัปโหลดสำเร็จ" ขั้นตอนคร่าว ๆ มีดังนี้

  1. ไคลเอนต์ส่งคำขอ POST ไปยังเซิร์ฟเวอร์เพื่อ "จอง" การอัปโหลดใหม่ พร้อมแจ้งขนาดไฟล์ทั้งหมด เซิร์ฟเวอร์จะตอบกลับ URL เฉพาะสำหรับไฟล์นั้น
  2. ไคลเอนต์ทยอยส่งข้อมูลเป็นชิ้น ๆ ด้วยคำขอ PATCH ไปยัง URL นั้น พร้อมระบุ offset ปัจจุบันในเฮดเดอร์
  3. ถ้าการเชื่อมต่อขาดระหว่างทาง ไคลเอนต์สามารถส่งคำขอ HEAD ไปถาม URL เดิมเพื่อเช็กว่าตอนนี้เซิร์ฟเวอร์มีข้อมูลถึงไบต์ไหนแล้ว
  4. จากนั้นไคลเอนต์ส่ง PATCH ต่อจาก offset ที่ได้รับ โดยไม่ต้องส่งข้อมูลเดิมซ้ำเลย
จุดสำคัญ: ฝั่งเซิร์ฟเวอร์ต้องมีที่เก็บสถานะการอัปโหลด (เช่น ไฟล์ cache หรือ Redis) เพื่อจำว่าแต่ละ upload ID ไปถึงไบต์ไหนแล้ว ไลบรารี tus-php จัดการส่วนนี้ให้อัตโนมัติ

04ติดตั้งและสร้าง Server ด้วย tus-php

เริ่มจากติดตั้งไลบรารีผ่าน Composer

terminal
composer require ankitpokhrel/tus-php

โค้ดด้านล่างคือ tus server แบบมินิมอล ที่รับคำขอทุก method ของโปรโตคอล tus แล้วจัดการให้เองทั้งหมด

server.php
<?php

require __DIR__ . '/vendor/autoload.php';

use TusPhp\Tus\Server as TusServer;

// ใช้ 'file' cache สำหรับเริ่มต้น (เปลี่ยนเป็น 'redis' ได้เมื่อขึ้น production)
$server = new TusServer('file');

$server->setApiPath('/files')
       ->setUploadDir(__DIR__ . '/uploads');

$response = $server->serve();
$response->send();

exit(0);

เพียงเท่านี้ server.php ตัวนี้ก็พร้อมรับคำขอ OPTIONS, POST, HEAD, PATCH และ DELETE ตามสเปกของ tus แล้ว แต่ในงานจริงเราอยากผูกมันเข้ากับเฟรมเวิร์กที่ใช้อยู่มากกว่า ในส่วนถัดไปจะพาไปดูทั้ง CodeIgniter 4 และ Laravel

05ผูก tus-php เข้ากับ CodeIgniter 4

ใน CI4 เราสามารถสร้าง Controller ขึ้นมาตัวเดียวเพื่อรับทุกคำขอที่เกี่ยวกับการอัปโหลด แล้วส่งต่อให้ tus-php จัดการ จากนั้นแปลง response ของ tus-php กลับมาเป็น response ของ CI4

app/Controllers/UploadController.php
<?php

namespace App\Controllers;

use TusPhp\Tus\Server as TusServer;

class UploadController extends BaseController
{
    public function handle()
    {
        $server = new TusServer('file');
        $server->setApiPath('/upload/files')
               ->setUploadDir(WRITEPATH . 'uploads');

        $response = $server->serve();

        // ส่ง response ที่ tus-php สร้างไว้กลับไปตรง ๆ
        return $this->response
            ->setStatusCode($response->getStatusCode())
            ->setBody($response->getContent());
    }
}

จากนั้นเปิดทางให้ทุก HTTP method ที่ tus ต้องใช้วิ่งเข้ามาที่ Controller เดียวกัน ใน app/Config/Routes.php

app/Config/Routes.php
$routes->match(
    ['get', 'post', 'patch', 'head', 'options', 'delete'],
    'upload/files(.*)',
    'UploadController::handle'
);

เก็บไฟล์ไว้ใต้ WRITEPATH ตามธรรมเนียมของ CI4 ก็ปลอดภัยกว่า เพราะโฟลเดอร์นี้อยู่นอก document root และคุมสิทธิ์การเข้าถึงได้ง่ายกว่าโฟลเดอร์ใน public/

06ผูก tus-php เข้ากับ Laravel

แนวคิดเดียวกันสามารถนำไปใช้กับ Laravel ได้โดยประกาศ route แบบ closure ที่ดักทุก method และทุก path ย่อยภายใต้ /files

routes/web.php
use TusPhp\Tus\Server as TusServer;
use Illuminate\Support\Facades\Route;

Route::match(
    ['get', 'post', 'patch', 'head', 'options', 'delete'],
    '/files/{any?}',
    function () {
        $server = new TusServer('file');
        $server->setApiPath('/files')
               ->setUploadDir(storage_path('app/uploads'));

        $response = $server->serve();

        return response($response->getContent(), $response->getStatusCode())
            ->withHeaders($response->headers->all());
    }
)->where('any', '.*');

ข้อดีของการใช้ storage_path('app/uploads') คือไฟล์ที่กำลังอัปโหลดอยู่จะไม่ถูกเข้าถึงโดยตรงจากเว็บ ต้องผ่าน Controller หรือ route ที่เรากำหนดเองเท่านั้น ตรงกับแนวทาง security ที่ Laravel แนะนำสำหรับไฟล์ที่ยังไม่พร้อมเผยแพร่

07ฝั่ง Client: อัปโหลดแบบ Resumable ด้วย tus-js-client

ฝั่งหน้าเว็บ เราไม่จำเป็นต้องเขียน logic เรื่อง offset หรือ retry เองเลย เพราะมีไลบรารี tus-js-client ที่คุยกับ tus server ตามสเปกให้ครบแล้ว สิ่งที่เราต้องทำคือสร้าง instance ของ tus.Upload แล้วกำหนด endpoint กับ callback ต่าง ๆ

upload-form.html
<input type="file" id="fileInput" />
<progress id="uploadProgress" value="0" max="100"></progress>
<button id="pauseBtn">Pause</button>
upload.js
import * as tus from "tus-js-client";

let upload;

document.getElementById("fileInput").addEventListener("change", (e) => {
  const file = e.target.files[0];

  upload = new tus.Upload(file, {
    endpoint: "/upload/files",
    retryDelays: [0, 3000, 5000, 10000],
    metadata: {
      filename: file.name,
      filetype: file.type,
    },
    onError: (error) => console.error("Upload failed:", error),
    onProgress: (bytesSent, bytesTotal) => {
      const percent = ((bytesSent / bytesTotal) * 100).toFixed(1);
      document.getElementById("uploadProgress").value = percent;
    },
    onSuccess: () => {
      console.log("Upload finished:", upload.url);
    },
  });

  // ถ้ามี upload ที่ค้างไว้ก่อนหน้า ให้ resume ต่อแทนเริ่มใหม่
  upload.findPreviousUploads().then((previousUploads) => {
    if (previousUploads.length) {
      upload.resumeFromPreviousUpload(previousUploads[0]);
    }
    upload.start();
  });
});

document.getElementById("pauseBtn").addEventListener("click", () => {
  if (upload) upload.abort();
});

เมธอด findPreviousUploads() คือกุญแจสำคัญของ "resume" ฝั่งไคลเอนต์ มันจะเช็ก localStorage ของเบราว์เซอร์ว่าไฟล์นี้เคยเริ่มอัปโหลดไว้หรือยัง ถ้าเจอก็เรียก resumeFromPreviousUpload() เพื่อสานต่อจากจุดเดิมทันที โดยที่ผู้ใช้ไม่ต้องทำอะไรเพิ่มเลย แม้จะปิดแท็บแล้วเปิดใหม่ก็ตาม

08REST Endpoint ของ tus Server

เพื่อให้เห็นภาพว่าแต่ละคำขอ HTTP ทำหน้าที่อะไรบ้างในวงจรของ tus สรุปไว้ในตารางนี้

MethodEndpointหน้าที่
OPTIONS/filesถาม server ว่ารองรับฟีเจอร์อะไรบ้าง เช่น ขนาดไฟล์สูงสุด
POST/filesเริ่มอัปโหลดใหม่ แจ้งขนาดไฟล์ทั้งหมด ได้ URL เฉพาะกลับมา
HEAD/files/{id}เช็ก offset ปัจจุบันของไฟล์ที่กำลังอัปโหลดอยู่
PATCH/files/{id}ส่งข้อมูล chunk ต่อจาก offset เดิมไปเรื่อย ๆ จนครบ
DELETE/files/{id}ยกเลิกและล้าง upload ที่ค้างอยู่ ใช้ทำความสะอาด storage

09ข้อดี ข้อจำกัด และเมื่อไหร่ควรใช้

tus เหมาะมากกับงานที่ผู้ใช้ต้องอัปโหลดไฟล์ขนาดใหญ่ เช่น ระบบอัปโหลดวิดีโอ ไฟล์ backup หรือไฟล์ CAD แต่ก็ไม่ใช่ทุกโปรเจกต์ที่จำเป็นต้องใช้ ลองดูข้อพิจารณาเหล่านี้ก่อนตัดสินใจ

  • เหมาะ: ไฟล์ขนาดใหญ่กว่า 50-100 MB ขึ้นไป หรือกลุ่มผู้ใช้ที่เน็ตไม่เสถียร เช่น ใช้งานผ่านมือถือนอกสถานที่
  • เหมาะ: ระบบที่ต้องการให้ผู้ใช้สลับอุปกรณ์ได้ เช่น เริ่มอัปโหลดจากโน้ตบุ๊ก แล้วไปต่อจากมือถือ
  • ไม่จำเป็น: ฟอร์มอัปโหลดรูปโปรไฟล์หรือเอกสารขนาดไม่กี่ MB การใช้ multipart/form-data แบบปกติยังคุ้มค่ากว่าในแง่ความซับซ้อน
  • ข้อควรระวัง: ต้องวางแผนเรื่อง storage สำหรับ chunk ที่ค้างไว้ และตั้งกลไกลบไฟล์ที่ไม่มีใครมา resume ต่อเป็นเวลานาน เพื่อไม่ให้ดิสก์เต็ม

10สรุป

การอัปโหลดไฟล์ใหญ่ไม่จำเป็นต้องเป็นฝันร้ายของผู้ใช้อีกต่อไป ด้วย tus protocol และไลบรารี tus-php เราสามารถเปลี่ยนการอัปโหลดแบบ "พังแล้วเริ่มใหม่ทั้งหมด" ให้กลายเป็น "หยุดตรงไหน ก็ต่อจากตรงนั้น" ได้ โดยที่ไม่ต้องเขียนกลไก chunk และ offset เองตั้งแต่ศูนย์ ไม่ว่าจะใช้ CodeIgniter 4 หรือ Laravel แนวคิดก็เหมือนกันคือสร้าง endpoint เดียวให้รับทุก method ของ tus แล้วปล่อยให้ไลบรารีจัดการส่วนที่เหลือ

สำหรับใครที่กำลังจะเริ่มฟีเจอร์อัปโหลดไฟล์ขนาดใหญ่ในโปรเจกต์ถัดไป ลองเริ่มจากการรันตัวอย่าง server แบบ minimal ในบทความนี้ดูก่อน แล้วค่อยปรับให้เข้ากับ authentication และ business logic ของระบบจริง



PHP CI MANIA - PHP Code Generator 

โปรแกรมช่วยสร้างโค้ด "ลดเวลาการเขียนโปรแกรม"
ราคาสุดคุ้ม  
http://www.phpcodemania.com

เบื้องหลัง API Artisan: สร้าง Laravel API ที่นักพัฒนาอยากใช้จริง

โดย CyberMAN


PHP Code Mania · Laravel API

เบื้องหลัง API Artisan: สร้าง Laravel API ที่นักพัฒนาอยากใช้จริง

ตั้งแต่ออกแบบ endpoint แรก ไปจนถึง versioning และการเขียนเทสต์ — เคล็ดลับที่ทำให้ API ของคุณไม่ใช่แค่ "ใช้งานได้" แต่เป็น API ที่ทีมอื่นเปิดเอกสารแล้วยิ้ม

~/projects/api-artisan
$ php artisan make:controller UserController --api
INFO  Controller [app/Http/Controllers/UserController.php] created successfully.

$ php artisan make:resource UserResource
INFO  Resource [app/Http/Resources/UserResource.php] created successfully.

# เริ่มต้นเหมือนช่างฝีมือ: เตรียมเครื่องมือก่อนลงมือ

คำว่า Artisan ใน Laravel ไม่ได้แปลว่า "เครื่องมือบรรทัดคำสั่ง" เพียงอย่างเดียว แต่มันสะท้อนปรัชญาของเฟรมเวิร์กนี้ทั้งหมด นั่นคือ การเขียนโค้ดควรเป็นงานฝีมือที่ประณีต ไม่ใช่แค่โค้ดที่ "รันผ่าน" แล้วจบ และไม่มีที่ไหนที่ปรัชญานี้สำคัญเท่ากับตอนที่คุณกำลังออกแบบ API เพราะ API ที่คุณสร้างวันนี้ จะกลายเป็นสัญญา (contract) ที่ทีม frontend, แอปมือถือ หรือแม้แต่ระบบภายนอกต้องพึ่งพาไปอีกหลายปี

บทความนี้เขียนขึ้นสำหรับนักพัฒนาที่คุ้นเคยกับ CodeIgniter 4 หรือเพิ่งเริ่มจับ Laravel และอยากเข้าใจว่า "API ที่ดี" ในมุมมองของ Laravel ควรหน้าตาเป็นอย่างไร เราจะไล่ตั้งแต่การวางแผน ไปจนถึงเรื่อง authentication, validation, performance และการเขียนเทสต์ — ครบทุกขั้นตอนที่ทำให้ API ของคุณ "พร้อมใช้งานจริง" ไม่ใช่แค่ดีโม

01

วางแผนก่อนเขียนโค้ดสักบรรทัด

นักพัฒนามือใหม่จำนวนมากเปิด editor แล้วเริ่มเขียน route ทันที แต่ช่างฝีมือที่ดีจะร่างแบบก่อนลงมือ ก่อนเขียนโค้ด ให้ถามตัวเองสามข้อ: API นี้มีไว้เพื่ออะไร, resource อะไรบ้างที่จะเปิดให้เข้าถึง และใครคือผู้ใช้งานปลายทาง (mobile app, SPA หรือระบบภายนอก)

เมื่อตอบคำถามเหล่านี้ได้แล้ว ให้ออกแบบ endpoint ตามหลัก RESTful คือยึด resource เป็นหลักและใช้ HTTP method สื่อความหมายของการกระทำ เช่น GET /api/users สำหรับดึงรายการ และ POST /api/users สำหรับสร้างใหม่ แทนที่จะตั้งชื่อ endpoint แบบ /api/getAllUsers ซึ่งไม่ตรงตามแบบแผนที่นักพัฒนาส่วนใหญ่คุ้นเคย

เคล็ดลับ: เขียนเอกสาร API ไว้ตั้งแต่ก่อนเริ่มโค้ดจริง แม้จะเป็นแค่โน้ตสั้น ๆ ก็ช่วยให้ทีมเห็นภาพตรงกันก่อนที่จะแก้โครงสร้างทีหลัง ซึ่งมีต้นทุนสูงกว่ามาก

02

ใช้ Resource Controller จัดระเบียบ CRUD

Laravel มีคำสั่งสร้าง controller สำหรับ API โดยเฉพาะ ซึ่งจะมาพร้อมเมธอดมาตรฐานครบทั้ง 5 ตัวสำหรับ CRUD โดยไม่มีเมธอดสำหรับแสดงฟอร์ม (เพราะ API ไม่ต้องการ view)

terminal
// สร้าง controller แบบ API (ไม่มี create/edit form)
php artisan make:controller UserController --api

คำสั่งนี้จะสร้างเมธอด index, store, show, update, destroy ให้พร้อมใช้ จากนั้นคุณแค่เติม logic เข้าไป โดยให้แต่ละเมธอดทำหน้าที่เดียวให้ชัดเจน ไม่ปนกัน

UserController.php
public function index() {
    return UserResource::collection(User::paginate(10));
}

public function show(User $user) {
    return new UserResource($user);
}

public function store(StoreUserRequest $request) {
    $user = User::create($request->validated());
    return new UserResource($user);
}
03

แปลง Model เป็น JSON ด้วย API Resource

ปัญหาคลาสสิกของมือใหม่คือการ return $user; ตรง ๆ จาก Eloquent ซึ่งจะส่งทุกคอลัมน์ในตาราง รวมถึง password hash หรือ token ที่ไม่ควรหลุดออกไปด้วย API Resource คือชั้นที่ทำหน้าที่ "คัดและจัดรูป" ข้อมูลก่อนส่งออกไปเป็น JSON

terminal
php artisan make:resource UserResource
UserResource.php
public function toArray($request) {
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'joined_at' => $this->created_at->format('Y-m-d'),
    ];
}

ข้อดีคือเมื่อโครงสร้างฐานข้อมูลเปลี่ยน คุณแก้แค่ที่เดียวในไฟล์ Resource โดยที่ฝั่ง client ไม่รู้สึกถึงผลกระทบเลย

04

Validate ทุกครั้งด้วย Form Request

อย่าไว้ใจข้อมูลที่ส่งเข้ามาจาก client เด็ดขาด ไม่ว่าจะเป็นแอปมือถือของทีมคุณเองก็ตาม Laravel แนะนำให้แยก logic การตรวจสอบข้อมูลออกจาก controller ไปไว้ในคลาส Form Request โดยเฉพาะ เพื่อให้ controller อ่านง่ายและ validation rule นำกลับมาใช้ซ้ำได้

terminal
php artisan make:request StoreUserRequest
StoreUserRequest.php
public function rules() {
    return [
        'name'  => 'required|string|max:255',
        'email' => 'required|email|unique:users,email',
    ];
}
05

ยืนยันตัวตนด้วย Sanctum

สำหรับ API ที่ให้บริการ SPA หรือแอปมือถือ Laravel Sanctum คือตัวเลือกที่เบาและตั้งค่าง่ายกว่า OAuth เต็มรูปแบบ เหมาะกับโปรเจกต์ส่วนใหญ่ที่ไม่ได้ต้องการระบบสิทธิ์ซับซ้อนระดับองค์กร

terminal
composer require laravel/sanctum
routes/api.php
Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
    Route::get('/profile', [ProfileController::class, 'show']);
});

การห่อ route ไว้ใน middleware group แบบนี้ ทำให้คุณจัดการ authentication และ rate limiting ของหลาย endpoint พร้อมกันได้จากจุดเดียว ไม่ต้องเขียนซ้ำในทุกเมธอด

06

คุม Performance ด้วย Pagination และ Eager Loading

API ที่ตอบกลับข้อมูลทั้งตารางในครั้งเดียวคือระเบิดเวลา เพราะเมื่อข้อมูลในฐานข้อมูลโตขึ้น response time จะยิ่งช้าลงเรื่อย ๆ ใช้ paginate() เพื่อจำกัดจำนวนต่อหน้าเสมอ

UserController.php
// ดึง user พร้อม posts ในคำสั่งเดียว แทนการ query วนลูป
$users = User::with('posts')->paginate(10);

การเรียก with('posts') คือ eager loading ที่ช่วยแก้ปัญหา N+1 query ซึ่งเป็นสาเหตุอันดับต้น ๆ ที่ทำให้ API ของมือใหม่ช้าโดยไม่รู้ตัว เพราะถ้าไม่ใส่ Laravel จะยิง query แยกไปดึง posts ของ user แต่ละคนทีละแถว

07

ตอบ Error ให้สม่ำเสมอ และเปิด CORS อย่างปลอดภัย

ฝั่ง client จะเขียนโค้ดจัดการ error ได้ง่ายขึ้นมาก ถ้า API ของคุณตอบกลับ error format เดียวกันทุกครั้ง ไม่ใช่บางจุด throw exception เป็น HTML บางจุดเป็น JSON ปนกัน Laravel มีระบบ exception handler กลางที่ช่วยควบคุมเรื่องนี้ได้ในจุดเดียว

ส่วนเรื่อง CORS หากต้องเปิดให้ frontend จากโดเมนอื่นเรียก API ได้ ให้ตั้งค่าผ่านไฟล์ config/cors.php โดยควรระบุโดเมนที่อนุญาตให้ชัดเจน แทนที่จะเปิดกว้างด้วย '*' ในระบบที่ใช้งานจริง เพราะนั่นเท่ากับเปิดให้เว็บไซต์ใดก็ได้เรียก API ของคุณ

08

Version API ไว้ตั้งแต่วันแรก

วันหนึ่งคุณจะต้องเปลี่ยนโครงสร้าง response แน่นอน ไม่ว่าจะเพิ่มฟิลด์หรือลบฟิลด์ที่ไม่ใช้แล้ว การมี version ใน path ตั้งแต่เริ่มต้น ช่วยให้คุณปล่อยเวอร์ชันใหม่ได้โดยไม่ทำให้แอปเวอร์ชันเก่าที่ยังเรียก API อยู่พังกะทันหัน

routes/api.php
Route::prefix('v1')->group(function () {
    Route::get('/users', [UserController::class, 'index']);
});
09

เขียน Test ไว้ดักก่อนโค้ดพัง

Laravel มีเครื่องมือทดสอบ API ในตัวที่ใช้งานง่ายมาก แค่ยิง request จำลองแล้วตรวจสอบ response ที่ได้ การมีเทสต์เพียงไม่กี่เคสสำหรับ endpoint สำคัญ ช่วยให้คุณกล้า refactor โค้ดในอนาคตโดยไม่ต้องนั่งทดสอบมือทุกครั้ง

UserApiTest.php
public function test_user_can_register() {
    $response = $this->postJson('/api/register', [
        'name'  => 'CyberMan',
        'email' => 'dev@example.com',
    ]);

    $response->assertStatus(201);
}
10

ปิดท้ายด้วยเอกสาร API

API ที่ไม่มีเอกสาร คือ API ที่ทีมอื่นกลัวจะใช้ แพ็กเกจอย่าง l5-swagger ช่วยให้คุณเขียนคอมเมนต์รูปแบบ OpenAPI ไว้เหนือแต่ละเมธอด แล้วระบบจะ generate หน้าเอกสารแบบ interactive ให้อัตโนมัติ ทำให้นักพัฒนาฝั่งอื่นทดลองยิง request ได้จากเบราว์เซอร์โดยไม่ต้องถามคุณซ้ำ ๆ

สรุปเช็กลิสต์: 8 เรื่องที่ API ของคุณควรมี

หัวข้อสิ่งที่ควรทำเครื่องมือ/แพ็กเกจ
โครงสร้าง endpointยึดหลัก RESTful ตาม resource และ HTTP methodroute:api
รูปแบบข้อมูลส่งออกใช้ Resource คัดและจัดรูป JSON เสมอmake:resource
ตรวจสอบข้อมูลขาเข้าแยก rule ไว้ใน Form Requestmake:request
การยืนยันตัวตนใช้ token-based auth ที่เบาและพอดีกับงานlaravel/sanctum
จำกัดการเรียกใช้ตั้ง rate limit ป้องกัน abusethrottle middleware
ความเร็วpaginate ทุก list และ eager load ความสัมพันธ์with() / paginate()
ความเข้ากันได้ย้อนหลังใส่ version ใน path ตั้งแต่ต้นRoute::prefix
ความมั่นใจในการแก้โค้ดเขียนเทสต์ครอบ endpoint สำคัญpostJson()

สรุป: API ที่ดีคืองานฝีมือ ไม่ใช่ของสำเร็จรูป

ทั้งหมดนี้ไม่ใช่กฎตายตัวที่ต้องทำให้ครบในวันแรก แต่เป็นแนวทางที่ค่อย ๆ เก็บเพิ่มได้ทีละข้อ เริ่มจาก Resource Controller และ API Resource ก่อน เพราะสองตัวนี้จะวางรากฐานให้โค้ดอ่านง่าย จากนั้นค่อยเติม validation, authentication และ rate limiting เข้าไปตามความจำเป็นของโปรเจกต์

สำหรับใครที่คุ้นเคยกับ CodeIgniter 4 อยู่แล้ว แนวคิดเรื่อง resource-based routing, การแยก validation ออกจาก controller หรือการ version API ก็นำไปปรับใช้กับ CI4 ได้เช่นกัน เพียงแค่ syntax และชื่อคลาสจะต่างกันไปตามแต่ละเฟรมเวิร์ก แก่นของหลักการยังเหมือนเดิม


Pinion: Resumable File Upload อัปโหลดไฟล์ขนาดใหญ่โดยไม่ต้องเริ่มใหม่

โดย CyberMAN



📦 PHP / CodeIgniter 4 / Laravel

Pinion: Resumable File Upload
อัปโหลดไฟล์ขนาดใหญ่โดยไม่ต้องเริ่มใหม่

ใช้ tus protocol + tus-php library แก้ปัญหาการอัปโหลดไฟล์ที่ค้างกลางทาง ครั้งเดียวจบ

PHPCodeIgniter 4LaravelFile Uploadtus-php

🤔 ปัญหาที่นักพัฒนา PHP ทุกคนเคยเจอ

ลองนึกภาพสถานการณ์นี้ดู — ผู้ใช้งานของคุณพยายามอัปโหลดวิดีโอขนาด 2 GB ขึ้นไปยังเว็บแอปของคุณ รออยู่นาน 45 นาที แล้วอินเทอร์เน็ตก็ดับ หรือเบราว์เซอร์ค้าง ผลลัพธ์คือ? ต้องเริ่มอัปโหลดใหม่ตั้งแต่ต้น ซึ่งเป็นประสบการณ์ที่แย่มากสำหรับผู้ใช้

ปัญหานี้พบได้บ่อยมากในระบบที่ต้องรับไฟล์ขนาดใหญ่ เช่น ระบบ LMS ที่อัปโหลดวิดีโอบทเรียน, ระบบจัดการเอกสารของหน่วยงานภาครัฐ, หรือแอปพลิเคชัน Creative ที่รับไฟล์ RAW จากกล้อง

💡 แนวคิดหลัก: แทนที่จะอัปโหลดไฟล์ทั้งก้อนในครั้งเดียว ให้แบ่งไฟล์ออกเป็น chunks เล็ก ๆ แล้วส่งทีละก้อน ถ้าขาดกลางทาง ครั้งหน้าก็เริ่มต่อจากก้อนที่ค้างไว้ได้เลย

🔌 tus Protocol คืออะไร?

tus (อ่านว่า "tuːs") คือ open protocol สำหรับการอัปโหลดไฟล์แบบ resumable โดยเฉพาะ มันถูกออกแบบมาให้รองรับการหยุดกลางทาง ไม่ว่าจะโดยตั้งใจ (ผู้ใช้กด pause) หรือโดยอุบัติเหตุ (เน็ตหลุด, เบราว์เซอร์ปิด) แล้วสามารถกลับมาต่อจากจุดเดิมได้

จุดเด่นที่น่าสนใจอีกอย่างคือ คุณสามารถเริ่มอัปโหลดจากแล็ปท็อป แล้วมาต่อบนมือถือได้ เพราะ tus track ความคืบหน้าด้วย upload ID ที่ unique บน server ไม่ใช่บน device

📦 tus-php: Library ที่เราจะใช้

tus-php คือ pure PHP library ที่ implement tus protocol v1.0.0 ครบทั้ง server และ client โดยไม่ผูกติดกับ framework ใด ๆ (framework agnostic) จึงใช้กับทั้ง CodeIgniter 4 และ Laravel ได้สะดวก

ติดตั้งผ่าน Composer

terminal
composer require ankitpokhrel/tus-php

⚙️ หลักการทำงานแบบ Step by Step

01

Client แบ่งไฟล์เป็น Chunks

JavaScript ฝั่ง client แบ่งไฟล์ออกเป็นก้อนเล็ก ๆ (เช่น 5 MB ต่อก้อน) แล้วส่งทีละ request

02

Server เก็บ Offset

Server (tus-php) เก็บค่า offset ไว้ใน Redis หรือ Cache ว่าไฟล์อัปโหลดมาถึงไบต์ที่เท่าไรแล้ว

03

เกิดการขัดจังหวะ? ต่อได้เลย

Client ส่ง HEAD request มาถามว่า server รับข้อมูลถึงไหนแล้ว แล้วส่ง chunk ที่เหลือต่อทันที

04

ประกอบ Chunks เป็นไฟล์สมบูรณ์

เมื่อรับครบทุก chunk แล้ว server จะ merge เป็นไฟล์ต้นฉบับที่สมบูรณ์

🖥️ ฝั่ง Server: สร้าง TUS Endpoint

ไฟล์นี้ทำหน้าที่เป็น tus server — รับ request จาก client และจัดการ chunk ทั้งหมด ตัวอย่างด้านล่างใช้ Redis เป็น storage สำหรับเก็บ upload metadata

tus-server.php
<?php

require 'vendor/autoload.php';

// สร้าง TUS server โดยใช้ Redis เก็บ upload state
$server = new \TusPhp\Tus\Server('redis');

// กำหนด path ที่จะเก็บไฟล์
$server->setUploadDir('/var/www/uploads');

// กำหนดขนาด chunk สูงสุด (5 MB)
$server->setMaxUploadSize(5 * 1024 * 1024);

// ให้ server จัดการ request ทั้งหมด
$response = $server->serve();

$response->send();

exit(0); // จำเป็นต้อง exit เพื่อหยุด PHP process

ตั้งค่า Nginx ให้ชี้มาที่ server

nginx.conf
location /uploads {
    try_files $uri $uri/ /tus-server.php?$query_string;
}

🚀 ใช้กับ Laravel: ผ่าน Route

ใน Laravel ไม่จำเป็นต้องแก้ไข Nginx config ตรง ๆ — ให้กำหนด route ใน routes/web.php หรือ routes/api.php แทน

routes/api.php (Laravel)
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TusUploadController;

// รองรับ TUS protocol ทุก HTTP method
Route::any('/upload/{token?}', [TusUploadController::class, 'handle'])
    ->where('token', '[0-9a-zA-Z\-]+');
app/Http/Controllers/TusUploadController.php
<?php

namespace App\Http\Controllers;

use TusPhp\Tus\Server;

class TusUploadController extends Controller
{
    public function handle()
    {
        $server = new Server('redis');
        $server->setUploadDir(storage_path('app/uploads'));

        $response = $server->serve();
        $response->send();

        exit(0);
    }
}

🔥 ใช้กับ CodeIgniter 4

CI4 มี Router ที่ยืดหยุ่น สามารถ map ทุก HTTP method เข้า controller เดียวได้ง่าย

app/Config/Routes.php (CI4)
<?php

// รองรับทุก method สำหรับ TUS endpoint
$routes->add('upload/(:any)', 'TusUpload::handle/$1');
$routes->add('upload', 'TusUpload::handle');
app/Controllers/TusUpload.php (CI4)
<?php

namespace App\Controllers;

use CodeIgniter\Controller;
use TusPhp\Tus\Server;

class TusUpload extends Controller
{
    public function handle()
    {
        $server = new Server('redis');
        $server->setUploadDir(WRITEPATH . 'uploads');

        $response = $server->serve();
        $response->send();

        exit(0);
    }
}

🌐 ฝั่ง Client: HTML Form + Uppy.js

tus-php ทำงานร่วมกับ JavaScript client library ได้หลายตัว ที่ได้รับความนิยมสูงสุดคือ Uppy.js ซึ่ง support tus protocol แบบ built-in และมี UI สวยงาม พร้อม progress bar

upload.blade.php / upload.php
<!-- โหลด Uppy CSS -->
<link rel="stylesheet" href="https://releases.transloadit.com/uppy/v3.3.1/uppy.min.css">

<div id="uppy-dashboard"></div>

<!-- โหลด Uppy JS -->
<script src="https://releases.transloadit.com/uppy/v3.3.1/uppy.min.js"></script>
<script>
  const uppy = new Uppy.Uppy()
    .use(Uppy.Dashboard, { target: '#uppy-dashboard', inline: true })
    .use(Uppy.Tus, {
      // ชี้ endpoint ไปที่ tus server
      endpoint: '/upload',
      // ขนาด chunk: 5 MB
      chunkSize: 5 * 1024 * 1024
    });

  uppy.on('complete', (result) => {
    console.log('อัปโหลดสำเร็จ:', result.successful);
  });
</script>

📊 เปรียบเทียบวิธีการอัปโหลดแบบต่าง ๆ

วิธีการResumableไฟล์ขนาดใหญ่ความยากติดตั้งเหมาะกับ
HTML Form อย่างเดียว❌ ไม่รองรับ❌ จำกัดมากง่ายมากไฟล์เล็ก < 10 MB
PHP chunked upload (manual)⚠️ ทำเองได้⚠️ พอใช้ได้ยากมากระบบที่ต้อง custom เยอะ
tus-php + tus protocol✅ รองรับเต็ม✅ ไม่จำกัดปานกลางไฟล์ใหญ่, production
Cloud Storage (S3, GCS)✅ รองรับ✅ ดีมากซับซ้อนScale ใหญ่, enterprise

🔧 ต้องการอะไรบ้าง?

📋 System Requirements:
PHP 7.4+ | Redis (แนะนำ) หรือ File-based cache | Composer | Nginx/Apache พร้อม URL Rewrite
Componentรายละเอียดหมายเหตุ
tus-phpLibrary หลักinstall via Composer
Redisเก็บ upload stateใช้ file cache แทนได้
Uppy.jsJS client สำหรับ browserหรือ tus-js-client
Writeable folderเก็บ chunk และไฟล์สุดท้ายchmod 775

🎯 ทำอะไรหลังอัปโหลดเสร็จ?

tus-php มี event listener ให้คุณดักจับตอนที่ไฟล์ upload ครบ 100% ได้ เหมาะสำหรับ trigger งานต่อเนื่อง เช่น แปลงวิดีโอ, สร้าง thumbnail, หรือบันทึกข้อมูลลงฐานข้อมูล

After Upload Hook
<?php

$server->event()->addListener(
    \TusPhp\Events\TusEvent::UPLOAD_COMPLETE,
    function (\TusPhp\Events\TusEvent $event) {
        $filePath = $event->getFile()->getFilePath();
        $fileName = $event->getFile()->getName();

        // บันทึกข้อมูลลงฐานข้อมูล
        \App\Models\MediaModel::create([
            'filename' => $fileName,
            'path'     => $filePath,
            'size'     => filesize($filePath),
            'status'   => 'ready',
        ]);
    }
);

📝 สรุป

การอัปโหลดไฟล์ขนาดใหญ่เป็นโจทย์ที่ดูเหมือนง่ายแต่มีรายละเอียดซ่อนอยู่มาก โดยเฉพาะเมื่อต้องรองรับผู้ใช้ที่มีอินเทอร์เน็ตไม่เสถียร tus protocol + tus-php library ช่วยแก้ปัญหานี้ได้อย่างสง่างาม โดยคุณไม่ต้องสร้าง chunking logic ขึ้นมาเอง

สิ่งที่ได้จากบทความนี้สถานะ
เข้าใจหลักการ Resumable Upload✅ ครอบคลุม
ติดตั้งและตั้งค่า tus-php server✅ ครอบคลุม
ใช้งานกับ Laravel✅ ครอบคลุม
ใช้งานกับ CodeIgniter 4✅ ครอบคลุม
ตั้งค่า Uppy.js client✅ ครอบคลุม
After-upload event hook✅ ครอบคลุม

ขั้นตอนต่อไปที่แนะนำคือ เพิ่ม authentication middleware เพื่อป้องกัน upload endpoint, กำหนด file type validation ก่อนประกอบ chunk, และตั้งค่า cleanup job สำหรับไฟล์ chunk ที่อัปโหลดค้างนาน ๆ



PHP CI MANIA - PHP Code Generator 

โปรแกรมช่วยสร้างโค้ด "ลดเวลาการเขียนโปรแกรม"
ราคาสุดคุ้ม  
http://www.phpcodemania.com