ทำไมไม่เปิด 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 ทำอะไรได้บ้าง
เปิดดูตาราง

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

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

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

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

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

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

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

ถ้าแอปมีหลาย connection เช่นฐานหลักกับ replica สำหรับรายงาน ใส่ไว้ใน config ได้ หน้าจอจะมีตัวเลือก connection พร้อมป้ายบอกว่าแต่ละตัวอ่านอย่างเดียวหรือเขียนได้
'connections' => [
'default' => [],
'reporting' => ['mode' => 'read', 'label' => 'Reporting replica'],
],
connection ที่ไม่ได้ใส่ไว้ในรายการนี้ หน้า console เข้าไม่ถึงเลย
กันพลาดยังไง
แพ็กเกจกันหลายชั้น ชั้นไหนพลาด ชั้นถัดไปยังกันไว้
- บนหน้าจอ ป้ายข้างช่อง SQL บอกทันทีว่าคำสั่งที่พิมพ์อยู่เป็นแบบอ่านหรือเขียน ชั้นนี้มีไว้เตือนเท่านั้น ไม่ได้ใช้ตัดสิน
- ในแอป ก่อนรัน แพ็กเกจตัดข้อความในเครื่องหมายคำพูดและ comment ออก รับได้ทีละคำสั่ง แล้วดูคำแรกว่าเป็นคำสั่งอ่าน เขียน หรือคำสั่งต้องห้าม คำสั่งแก้โครงสร้างอย่าง
DROPALTERCREATETRUNCATEคำสั่งคุม session หรือ transaction และคำสั่งที่ทำงานนอก transaction อย่างSELECT … INTO OUTFILELOAD DATAหรือLOAD_FILE()รันไม่ได้ทุกโหมด - ในฐานข้อมูล คำสั่งอ่านรันใน transaction แบบ
READ ONLYที่มี timeout และ rollback ทุกครั้ง ถ้ามีคำสั่งเขียนหลุดผ่านชั้นที่แล้วมาได้ ฐานข้อมูลก็ปฏิเสธเอง ทั้ง Postgres และ InnoDB ของ MySQL - ยืนยันก่อนเขียน ลบแถวต้องพิมพ์ยืนยันทุกครั้ง ส่วน insert กับ update ตั้งให้ต้องยืนยันได้ด้วย
confirm_writesระบบออก token ผูกกับ SQL คำสั่งนั้น ถ้าแก้คำสั่งแม้แต่ตัวเดียว token ก็ใช้ไม่ได้ - จำกัดจำนวนแถว ผลลัพธ์หยุดที่
max_rows(ค่าเริ่มต้น 5,000)

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

บน 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




