Open source

laravel-db-console ดูข้อมูลในฐานข้อมูลผ่านหน้าเว็บของแอป Laravel แทน phpMyAdmin และ Adminer

แพ็กเกจที่เพิ่มหน้า /db-console ในแอป Laravel ให้เปิดดูตาราง กรองข้อมูล และรัน SQL ได้จากเบราว์เซอร์ อ่านอย่างเดียวเป็นค่าเริ่มต้น และใช้สิทธิ์ของแอปเองแทนรหัสผ่านฐานข้อมูล

เผยแพร่
เวลาอ่าน
7 นาที

ทำไมไม่เปิด phpMyAdmin หรือ Adminer

เวลาอยากดูข้อมูลในฐานข้อมูลของแอปจริง ๆ หลายทีมเปิด phpMyAdmin บน hosting หรือวาง Adminer ซึ่งเป็นไฟล์ PHP ไฟล์เดียวไว้บนเซิร์ฟเวอร์ ใช้ง่ายและคุ้นมือ แต่มีเรื่องที่ต้องระวังอยู่สามเรื่อง

  • อยู่นอกแอป ต้องติดตั้งแยก มีหน้า login ของตัวเอง และต้องกรอกรหัสผ่านฐานข้อมูลลงไป ใครได้รหัสนี้ไปก็เข้าได้ทั้งฐาน
  • ไฟล์ที่ลืมไว้ Adminer ที่วางไว้ใช้ครั้งเดียวแล้วลืมลบ ยังเปิดจากอินเทอร์เน็ตได้ต่อไปเรื่อย ๆ
  • เขียนได้ทุกอย่าง กดลบแถวผิดหรือรัน UPDATE ที่ลืม WHERE บน production ทีเดียวก็แก้กลับยาก

ถ้าชอบ Adminer อยู่แล้ว มีแพ็กเกจ laravel-adminer ที่ย้าย Adminer เข้ามาอยู่หลัง login ของแอป ผมเองก็เคยใช้ตัวนี้ก่อนเขียนแพ็กเกจนี้ แพ็กเกจนั้นแก้สองเรื่องแรกได้ แต่ Adminer ยังเขียนได้ทุกอย่างเหมือนเดิม

laravel-db-console ย้ายหน้านี้เข้ามาอยู่ในแอป ใช้ login ของแอปตัดสินว่าใครเข้าได้ ไม่ต้องรู้รหัสผ่านฐานข้อมูล และทุก connection เริ่มที่โหมดอ่านอย่างเดียว ถ้าจะให้เขียนได้ต้องเปิดเองทีละ connection

ใช้กับแอปแบบไหนได้

แพ็กเกจนี้ตั้งใจทำแคบ ๆ ถ้าแอปไม่ตรงเงื่อนไข หน้าจอจะบอกตรง ๆ ว่าขาดอะไร แทนที่จะทำงานได้ครึ่ง ๆ กลาง ๆ

  • ฐานข้อมูล MySQL 8, MariaDB หรือ PostgreSQL การกันเขียนอาศัยความสามารถของฐานข้อมูลเอง เช่น transaction แบบอ่านอย่างเดียว ถ้าเป็น SQLite หรือ SQL Server หน้าจอจะบอกชื่อ connection กับ driver ที่ไม่รองรับ
  • หน้าเว็บเป็น Inertia (v2 หรือ v3) + React 19 หน้า console เป็นหน้า Inertia ไม่ใช่ Blade และแอปต้อง build asset เองด้วย Vite แอปที่ขึ้นจาก React starter kit ของ Laravel ใช้ได้เลยโดยไม่ต้องแก้ไฟล์ของ starter kit
  • PHP 8.4 กับ Laravel 12 หรือ 13

ไม่ต้องมี Tailwind แพ็กเกจมี CSS ของตัวเองที่ครอบไว้ใต้ .dc-root จึงไม่ชนกับ CSS ของแอป

ติดตั้ง

composer require phattarachai/laravel-db-console
php artisan vendor:publish --tag=db-console-config
php artisan vendor:publish --tag=db-console-inertia
php artisan migrate

แล้วเพิ่ม alias ใน vite.config.js (หรือ vite.config.ts) ให้ Vite หาโค้ด React ของแพ็กเกจเจอ

import { fileURLToPath } from 'node:url'

export default defineConfig({
    // …plugin เดิมของแอป
    resolve: {
        alias: {
            '@db-console': fileURLToPath(
                new URL('./vendor/phattarachai/laravel-db-console/resources/js/db-console', import.meta.url),
            ),
        },
    },
})

รัน npm run build แล้วเปิด /db-console ได้เลย

คำสั่ง publish คัดลอกแค่ไฟล์หน้า resources/js/pages/DbConsole มาไว้ในแอป ถ้าแอปเป็น TypeScript ก็ได้ไฟล์ .tsx ส่วนโค้ดที่เหลืออยู่ใน vendor/ และเรียกผ่าน alias พอเราอัปเดตแพ็กเกจ หน้าจอก็อัปเดตตาม ไม่มีสำเนาสองชุดที่ต้องคอยตามแก้

ถ้าเปิดแล้วไม่ขึ้น ลองรัน

php artisan db-console:doctor

คำสั่งนี้เช็ก driver, migration, route, ไฟล์หน้า และ alias ของ Vite แล้วบอกว่าขาดข้อไหนและแก้ยังไง

ใครเปิดหน้านี้ได้

บนเครื่อง local เปิดได้เสมอ เพราะเป็นตอนที่เราเปิดไล่ปัญหาบนเครื่องตัวเอง ส่วนบน server อื่นต้องบอกเองว่าใครเข้าได้ ใส่ไว้ใน service provider

use Illuminate\Http\Request;
use Phattarachai\DbConsole\DbConsole;

public function boot(): void
{
    DbConsole::auth(fn (Request $request) => $request->user()?->isAdmin() === true);
}
  • ถ้าไม่ได้ตั้งไว้ แพ็กเกจจะลองใช้ gate ชื่อ viewDbConsole ถ้าแอปไม่มี gate นี้ก็ไม่ให้ใครเข้า
  • คนที่ยังไม่ login จะไปหน้า login ของแอป พอ login เสร็จก็กลับมาที่ console
  • คนที่ login แล้วแต่ไม่มีสิทธิ์จะได้ 403
  • ถ้าไม่อยากให้มีหน้านี้บน server ไหนเลย ตั้ง DB_CONSOLE_ENABLED=false แพ็กเกจจะไม่ลงทะเบียน route เลย URL จึงตอบ 404 เหมือนไม่มีหน้านี้

หน้า /db-console ทำอะไรได้บ้าง

เปิดดูตาราง

หน้า explorer แสดงรายชื่อตารางและข้อมูลในตาราง orders

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

ด้านขวาเป็นข้อมูลในตาราง เรียงตามคอลัมน์ได้ แบ่งหน้าจาก server ลากขอบคอลัมน์เพื่อปรับความกว้างได้ และ export หน้าที่เห็นอยู่เป็น CSV ได้

ดูโครงสร้างตาราง

แท็บโครงสร้างตาราง แสดงคอลัมน์ index และ foreign key

แท็บโครงสร้างบอกคอลัมน์ ชนิดข้อมูล ค่า null ค่าเริ่มต้น index และ foreign key ของตาราง เวลาเขียน migration หรือ query ใหม่ เปิดดูตรงนี้ได้เลยโดยไม่ต้องย้อนไปอ่านไฟล์ migration

กรองข้อมูลทีละคอลัมน์

ตัวกรองข้อมูลทีละคอลัมน์สองเงื่อนไข

เลือกคอลัมน์แล้วเลือกเงื่อนไขได้โดยไม่ต้องเขียน SQL ตัวเลือกเปลี่ยนตามชนิดข้อมูล ตัวเลขมี = < ≥ between in ข้อความมี contains, starts with, ends with วันที่มีตัวเลือกวัน ทุกเงื่อนไขกรองทั้งตารางบน server ผ่าน query builder ไม่ได้กรองแค่ข้อมูลที่โหลดมาหน้าเดียว

ตารางที่เลือก ตัวกรอง การเรียง และหน้าที่ดูอยู่ทั้งหมดเก็บไว้ใน URL กด refresh ก็กลับมาที่เดิม ส่งลิงก์ให้เพื่อนในทีมก็เห็นภาพเดียวกัน

ดูค่ายาว ๆ และ JSON

แผงด้านข้างแสดงค่า jsonb แบบจัดรูปแบบ

ดับเบิลคลิกเซลล์ไหนก็ได้ จะเปิดแผงด้านข้างที่แสดงค่าเต็ม ถ้าเป็น json หรือ jsonb จะจัดย่อหน้าและใส่สีให้อ่านง่าย สลับเป็นค่าดิบได้

คลิกขวาที่เซลล์

เมนูคลิกขวาบนคอลัมน์ foreign key

คลิกขวาที่เซลล์จะมีเมนูให้ดูค่า ไปที่แถวที่ foreign key ชี้ไป คัดลอกค่า คัดลอกทั้งแถวเป็น JSON หรือข้อความ และถ้า connection นั้นเขียนได้ ก็มีแก้ไขหรือลบแถวด้วย ตารางจึงไม่ต้องเสียที่ให้คอลัมน์ปุ่ม action

เปิดตาราง customers กรองเหลือแถวที่ foreign key ชี้ไป

คลิกที่ค่า foreign key หรือเลือกเมนู "ไปที่ customers.id" หน้าจอจะเปิดตาราง customers แล้วกรองเหลือแถวนั้นด้วยตัวกรอง id = 11 ธรรมดา อยากดูแถวอื่นต่อก็แค่ลบตัวกรองออก

รัน SQL

หน้า SQL console พร้อมผลลัพธ์

ช่องเขียน SQL มีสีไฮไลต์และปุ่มจัดรูปแบบ ผลลัพธ์บอกจำนวนคอลัมน์ จำนวนแถว และเวลาที่ใช้ กด EXPLAIN ดูแผนการ query ได้ query ที่ใช้บ่อยบันทึกเก็บไว้ได้ มีประวัติการรัน และสร้างลิงก์แชร์ query ให้คนอื่นในทีมเปิดดูได้ (ลิงก์หมดอายุใน 7 วัน)

หลาย connection

ตัวเลือก connection พร้อมป้ายโหมด

ถ้าแอปมีหลาย connection เช่นฐานหลักกับ replica สำหรับรายงาน ใส่ไว้ใน config ได้ หน้าจอจะมีตัวเลือก connection พร้อมป้ายบอกว่าแต่ละตัวอ่านอย่างเดียวหรือเขียนได้

'connections' => [
    'default' => [],
    'reporting' => ['mode' => 'read', 'label' => 'Reporting replica'],
],

connection ที่ไม่ได้ใส่ไว้ในรายการนี้ หน้า console เข้าไม่ถึงเลย

กันพลาดยังไง

แพ็กเกจกันหลายชั้น ชั้นไหนพลาด ชั้นถัดไปยังกันไว้

  1. บนหน้าจอ ป้ายข้างช่อง SQL บอกทันทีว่าคำสั่งที่พิมพ์อยู่เป็นแบบอ่านหรือเขียน ชั้นนี้มีไว้เตือนเท่านั้น ไม่ได้ใช้ตัดสิน
  2. ในแอป ก่อนรัน แพ็กเกจตัดข้อความในเครื่องหมายคำพูดและ comment ออก รับได้ทีละคำสั่ง แล้วดูคำแรกว่าเป็นคำสั่งอ่าน เขียน หรือคำสั่งต้องห้าม คำสั่งแก้โครงสร้างอย่าง DROP ALTER CREATE TRUNCATE คำสั่งคุม session หรือ transaction และคำสั่งที่ทำงานนอก transaction อย่าง SELECT … INTO OUTFILE LOAD DATA หรือ LOAD_FILE() รันไม่ได้ทุกโหมด
  3. ในฐานข้อมูล คำสั่งอ่านรันใน transaction แบบ READ ONLY ที่มี timeout และ rollback ทุกครั้ง ถ้ามีคำสั่งเขียนหลุดผ่านชั้นที่แล้วมาได้ ฐานข้อมูลก็ปฏิเสธเอง ทั้ง Postgres และ InnoDB ของ MySQL
  4. ยืนยันก่อนเขียน ลบแถวต้องพิมพ์ยืนยันทุกครั้ง ส่วน insert กับ update ตั้งให้ต้องยืนยันได้ด้วย confirm_writes ระบบออก token ผูกกับ SQL คำสั่งนั้น ถ้าแก้คำสั่งแม้แต่ตัวเดียว token ก็ใช้ไม่ได้
  5. จำกัดจำนวนแถว ผลลัพธ์หยุดที่ max_rows (ค่าเริ่มต้น 5,000)

ตัวป้องกันปฏิเสธคำสั่ง DELETE บน connection แบบอ่านอย่างเดียว

พิมพ์ DELETE บน connection ที่อ่านอย่างเดียว ปุ่มรันจะกดไม่ได้ ถ้ากดคีย์ลัด Cmd+Enter แอปก็ปฏิเสธพร้อมบอกเหตุผล ฐานข้อมูลไม่ได้รับคำสั่งนั้นเลย

ยืนยันก่อนลบแถวบน connection ที่เขียนได้

บน connection ที่เปิดให้เขียน หน้าจอจะโชว์ SQL ที่จะรันให้ดูก่อน และการลบแถวต้องพิมพ์ RUN ยืนยันเสมอ ตารางที่ไม่มี primary key แก้ไขไม่ได้ เพราะระบุแถวที่จะแก้ไม่ได้แน่นอน

ซ่อนตารางและปิดบังคอลัมน์

ค่าเริ่มต้นหน้า console แสดงทุกตารางตามจริง ถ้ามีตารางหรือคอลัมน์ที่ไม่อยากให้เห็น ตั้งไว้ใน config

'hidden_tables' => ['personal_access_tokens', 'sessions'],

'masked_columns' => ['users.password', '*.api_token'],

คอลัมน์ที่ปิดบังจะแสดงเป็น *** แพ็กเกจแทนค่าตั้งแต่ฝั่ง server เบราว์เซอร์จึงไม่เคยได้ค่าจริง คอลัมน์พวกนี้ไม่มีในตัวกรอง และแก้ไขไม่ได้ ส่วน SQL ที่อ้างถึงตารางที่ซ่อนไว้ แพ็กเกจปฏิเสธทั้งคำสั่ง

ค่าที่ตั้งบ่อย

ค่าใน config/db-console.php ค่าเริ่มต้น ใช้ทำอะไร
enabled true false แล้วไม่มี route เลย
path db-console path ของหน้า
defaults.mode read read หรือ write ต่อ connection
defaults.confirm_writes false ให้ insert/update ต้องพิมพ์ยืนยัน (ลบแถวต้องยืนยันเสมอ)
defaults.max_rows / timeout 5000 / 5000 จำนวนแถวสูงสุด และเวลาสูงสุดเป็นมิลลิวินาที
hidden_tables / masked_columns ว่าง ซ่อนตาราง และปิดบังคอลัมน์ ใช้ * ได้
history.keep_days 30 เก็บประวัติการรัน SQL กี่วัน
share.expires_days 7 อายุของลิงก์แชร์ query
locale ตามแอป มี en และ th

ข้อควรรู้

  • บน MySQL จำนวนแถวเป็นตัวเลขประมาณ แพ็กเกจอ่านจาก information_schema เพื่อให้เปิดหน้าได้เร็ว ไม่ต้องนับทั้งตาราง หน้าจอจึงแสดงเป็น ~40
  • บน MySQL timeout คุมแค่คำสั่ง SELECT คำสั่งเขียนบน connection ที่เปิดให้เขียน มีแค่ innodb_lock_wait_timeout คุมอยู่ ถ้า UPDATE ต้องไล่ทั้งตารางใหญ่ ก็จะรันจนเสร็จ ส่วน MariaDB และ Postgres คุมทั้งอ่านและเขียน
  • ตั้ง hidden_tables และ masked_columns ก่อนเปิดให้คนอื่นใช้ ค่าเริ่มต้นของทั้งสองค่าว่าง หน้า console จึงแสดงทุกอย่าง รวมถึงรหัสผ่านที่ hash แล้วและ token ต่าง ๆ
  • ประวัติและ query ที่บันทึกไว้อยู่ในฐานข้อมูลของแอป ในตาราง db_console_* แพ็กเกจลบประวัติเก่าให้ทุกวันด้วย db-console:prune ส่วน query ที่บันทึกไว้แพ็กเกจไม่ลบ
  • หน้าจอภาษาไทย ตั้ง locale เป็น th หรือปล่อยให้ตามภาษาของแอป ข้อความแก้ได้หลัง php artisan vendor:publish --tag=db-console-lang
  • ปรับสีให้เข้ากับแอป ตั้ง brand.accent เป็นสีหลักของแอป และ brand.scheme เป็น light, dark หรือ auto ที่ตามคลาส .dark ของแอป

เวอร์ชันที่รองรับ

  • PHP 8.4 ขึ้นไป
  • Laravel 12 และ 13
  • MySQL 8, MariaDB หรือ PostgreSQL
  • Inertia v2 หรือ v3 กับ React 19

โพสต์นี้อ้างอิงเวอร์ชัน 1.1.1 ถ้าใช้ 1.0 อยู่ อัปเดตแล้ว publish ไฟล์หน้าใหม่ด้วย php artisan vendor:publish --tag=db-console-inertia --force

GitHub: phattarachai/laravel-db-console

อ่านต่อ

ล่าสุด

ดูทั้งหมด →