[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"academy-blogs-th-1-1-all-file-upload-websocket-chat-all--*":3,"academy-blog-translations-fwc905ic8t3yry7":105},{"data":4,"page":92,"perPage":92,"totalItems":92,"totalPages":92},[5],{"alt":6,"collectionId":7,"collectionName":8,"content":9,"cover_image":10,"cover_image_path":11,"created":12,"created_by":13,"expand":14,"id":100,"keywords":101,"locale":74,"published_at":102,"scheduled_at":13,"school_blog":96,"short_description":103,"status":94,"title":6,"updated":104,"updated_by":13,"slug":97,"views":99},"EP.40 การเพิ่มฟีเจอร์อัปโหลดไฟล์ใน WebSocket Chat","sclblg987654321","school_blog_translations","\u003Ch3 data-pm-slice=\"1 1 []\">\u003Cspan>\u003Cstrong>ทำไมต้องมีฟีเจอร์อัปโหลดไฟล์ใน WebSocket Chat?\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Cp>\u003Cspan>ในระบบแชทที่ทันสมัย ผู้ใช้ต้องการสามารถ \u003Cstrong>แชร์ไฟล์ รูปภาพ และเอกสาร\u003C\u002Fstrong> ระหว่างกันได้ การเพิ่มฟีเจอร์ \u003Cstrong>อัปโหลดไฟล์ผ่าน WebSocket\u003C\u002Fstrong> ทำให้ผู้ใช้สามารถส่งไฟล์ได้แบบเรียลไทม์โดยไม่ต้องโหลดหน้าใหม่ ซึ่งเหมาะสำหรับ:\u003C\u002Fspan>\u003C\u002Fp>\u003Cul data-spread=\"false\">\u003Cli>\u003Cspan>\u003Cstrong>ระบบแชทภายในองค์กร\u003C\u002Fstrong> ที่ต้องแชร์เอกสารและไฟล์งาน\u003C\u002Fspan>\u003C\u002Fli>\u003Cli>\u003Cspan>\u003Cstrong>แพลตฟอร์มโซเชียลมีเดีย\u003C\u002Fstrong> ที่ให้ผู้ใช้ส่งรูปภาพและวิดีโอ\u003C\u002Fspan>\u003C\u002Fli>\u003Cli>\u003Cspan>\u003Cstrong>บริการสนับสนุนลูกค้า\u003C\u002Fstrong> ที่ต้องส่งไฟล์แนบหรือภาพถ่ายปัญหาต่างๆ\u003C\u002Fspan>\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>\u003Cspan>\u003Cstrong>โครงสร้างของระบบอัปโหลดไฟล์ใน WebSocket Chat\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Col data-spread=\"false\">\u003Cli>\u003Cspan>\u003Cstrong>WebSocket Server\u003C\u002Fstrong> - รับไฟล์จากผู้ใช้และกระจายไปยังผู้ใช้ที่เกี่ยวข้อง\u003C\u002Fspan>\u003C\u002Fli>\u003Cli>\u003Cspan>\u003Cstrong>GraphQL API\u003C\u002Fstrong> - ใช้เพื่อจัดการการอัปโหลดไฟล์และเก็บเมตะดาต้า\u003C\u002Fspan>\u003C\u002Fli>\u003Cli>\u003Cspan>\u003Cstrong>File Storage\u003C\u002Fstrong> - จัดเก็บไฟล์ในเซิร์ฟเวอร์หรือ Cloud Storage (เช่น AWS S3 หรือ Firebase Storage)\u003C\u002Fspan>\u003C\u002Fli>\u003Cli>\u003Cspan>\u003Cstrong>Database (PostgreSQL \u002F MongoDB)\u003C\u002Fstrong> - เก็บข้อมูลไฟล์ เช่น URL และข้อมูลผู้ส่ง\u003C\u002Fspan>\u003C\u002Fli>\u003C\u002Fol>\u003Ch3>\u003Cspan>\u003Cstrong>ติดตั้งไลบรารีที่จำเป็น\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Cpre>\u003Ccode class=\"language-plaintext\">go get github.com\u002Fgorilla\u002Fwebsocket\ngo get github.com\u002Fminio\u002Fminio-go\u002Fv7\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3 data-pm-slice=\"1 1 []\">\u003Cspan>\u003Cstrong>การตั้งค่าฐานข้อมูลสำหรับเก็บข้อมูลไฟล์\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Cp>\u003Cspan>ไฟล์ \u003C\u002Fspan>\u003Ccode>\u003Cspan>schema.sql\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fp>\u003Cpre>\u003Ccode class=\"language-plaintext\">CREATE TABLE chat_files (\n    id SERIAL PRIMARY KEY,\n    room_id INTEGER NOT NULL,\n    sender TEXT NOT NULL,\n    file_url TEXT NOT NULL,\n    file_name TEXT NOT NULL,\n    file_type TEXT NOT NULL,\n    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3 data-pm-slice=\"1 1 []\">\u003Cspan>\u003Cstrong>การสร้าง GraphQL Schema สำหรับการอัปโหลดไฟล์\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Cp>\u003Cspan>ไฟล์ \u003C\u002Fspan>\u003Ccode>\u003Cspan>schema.graphql\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fp>\u003Cpre>\u003Ccode class=\"language-plaintext\">type Mutation {\n  uploadFile(roomID: ID!, sender: String!, fileName: String!, fileType: String!, fileContent: String!): ChatFile!\n}\n\ntype ChatFile {\n  id: ID!\n  roomID: ID!\n  sender: String!\n  fileURL: String!\n  fileName: String!\n  fileType: String!\n  timestamp: String!\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3 data-pm-slice=\"1 1 []\">\u003Cspan>\u003Cstrong>การสร้าง Resolver สำหรับการอัปโหลดไฟล์\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Cp>\u003Cspan>ไฟล์ \u003C\u002Fspan>\u003Ccode>\u003Cspan>resolver.go\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fp>\u003Cpre>\u003Ccode class=\"language-plaintext\">package main\n\nimport (\n    \"context\"\n    \"encoding\u002Fbase64\"\n    \"fmt\"\n    \"os\"\n    \"path\u002Ffilepath\"\n    \"time\"\n)\n\ntype ChatFile struct {\n    ID        int       `json:\"id\"`\n    RoomID    int       `json:\"roomID\"`\n    Sender    string    `json:\"sender\"`\n    FileURL   string    `json:\"fileURL\"`\n    FileName  string    `json:\"fileName\"`\n    FileType  string    `json:\"fileType\"`\n    Timestamp time.Time `json:\"timestamp\"`\n}\n\nfunc (r *Resolver) Mutation_uploadFile(ctx context.Context, roomID int, sender string, fileName string, fileType string, fileContent string) (ChatFile, error) {\n    decoded, err := base64.StdEncoding.DecodeString(fileContent)\n    if err != nil {\n        return ChatFile{}, err\n    }\n\n    savePath := filepath.Join(\"uploads\", fileName)\n    err = os.WriteFile(savePath, decoded, 0644)\n    if err != nil {\n        return ChatFile{}, err\n    }\n\n    fileURL := fmt.Sprintf(\"\u002Fuploads\u002F%s\", fileName)\n    file := ChatFile{RoomID: roomID, Sender: sender, FileURL: fileURL, FileName: fileName, FileType: fileType, Timestamp: time.Now()}\n    return file, nil\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3 data-pm-slice=\"1 1 []\">\u003Cspan>\u003Cstrong>การส่งไฟล์ผ่าน WebSocket\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Cp>\u003Cspan>ไฟล์ \u003C\u002Fspan>\u003Ccode>\u003Cspan>websocket_server.go\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fp>\u003Cpre>\u003Ccode class=\"language-plaintext\">package main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"fmt\"\n    \"net\u002Fhttp\"\n    \"github.com\u002Fgorilla\u002Fwebsocket\"\n)\n\ntype FileMessage struct {\n    RoomID    int    `json:\"roomID\"`\n    Sender    string `json:\"sender\"`\n    FileName  string `json:\"fileName\"`\n    FileType  string `json:\"fileType\"`\n    FileURL   string `json:\"fileURL\"`\n}\n\nvar upgrader = websocket.Upgrader{\n    CheckOrigin: func(r *http.Request) bool { return true },\n}\n\nfunc handleWebSocket(w http.ResponseWriter, r *http.Request) {\n    conn, _ := upgrader.Upgrade(w, r, nil)\n    defer conn.Close()\n    fmt.Println(\"Client connected\")\n\n    for {\n        _, msg, err := conn.ReadMessage()\n        if err != nil {\n            break\n        }\n        var fileMsg FileMessage\n        json.Unmarshal(msg, &amp;fileMsg)\n        fmt.Printf(\"Received file: %s from %s\\n\", fileMsg.FileName, fileMsg.Sender)\n    }\n}\n\nfunc main() {\n    http.HandleFunc(\"\u002Fws\", handleWebSocket)\n    fmt.Println(\"WebSocket Server Running on Port 8080\")\n    http.ListenAndServe(\":8080\", nil)\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3 data-pm-slice=\"1 1 []\">\u003Cspan>\u003Cstrong>การอัปโหลดไฟล์จากฝั่ง Client\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Cp>\u003Cspan>ไฟล์ \u003C\u002Fspan>\u003Ccode>\u003Cspan>client.js\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fp>\u003Cpre>\u003Ccode class=\"language-plaintext\">async function uploadFile(file) {\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n    reader.onload = async () =&gt; {\n        const base64Content = reader.result.split(\",\")[1];\n        const response = await fetch(\"\u002Fgraphql\", {\n            method: \"POST\",\n            headers: { \"Content-Type\": \"application\u002Fjson\" },\n            body: JSON.stringify({\n                query: `mutation { uploadFile(roomID: 1, sender: \"JohnDoe\", fileName: \"${file.name}\", fileType: \"${file.type}\", fileContent: \"${base64Content}\" ) { fileURL } }`\n            })\n        });\n        console.log(await response.json());\n    };\n}\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3 data-pm-slice=\"1 1 []\">\u003Cspan>\u003Cstrong>ท้าให้ลอง!\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Cp>\u003Cspan>ลองเพิ่ม \u003Cstrong>ระบบแสดงตัวอย่างไฟล์ก่อนอัปโหลด\u003C\u002Fstrong> เพื่อให้ผู้ใช้สามารถดูไฟล์ก่อนกดส่งไปยังห้องแชท\u003C\u002Fspan>\u003C\u002Fp>\u003Chr>\u003Ch3>\u003Cspan>\u003Cstrong>EP ถัดไป\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fh3>\u003Cp>\u003Cspan>ใน \u003Cstrong>EP.41\u003C\u002Fstrong>, เราจะเพิ่ม \u003Cstrong>ฟีเจอร์แสดงสถานะการพิมพ์ (Typing Indicator) ใน WebSocket Chat\u003C\u002Fstrong> 🚀\u003C\u002Fspan>\u003C\u002Fp>","51_11zon_r3kuowrjrz.webp","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclblg987654321\u002Fjcsygzeq0ejxspb\u002F51_11zon_r3kuowrjrz.webp","2026-03-04 08:51:11.103Z","",{"keywords":15,"locale":68,"school_blog":78},[16,23,28,33,38,43,48,53,58,63],{"collectionId":17,"collectionName":18,"created":19,"created_by":13,"id":20,"name":21,"updated":22,"updated_by":13},"sclkey987654321","school_keywords","2026-03-04 08:51:07.889Z","svzsiusj88bni77","Chat Media Upload","2026-04-10 16:14:32.854Z",{"collectionId":17,"collectionName":18,"created":24,"created_by":13,"id":25,"name":26,"updated":27,"updated_by":13},"2026-03-04 08:51:08.283Z","ux24tskiyudefze","Firebase Storage","2026-04-10 16:14:33.007Z",{"collectionId":17,"collectionName":18,"created":29,"created_by":13,"id":30,"name":31,"updated":32,"updated_by":13},"2026-03-04 08:51:08.494Z","bqukhjhwr4yxnts","AWS S3","2026-04-10 16:14:33.170Z",{"collectionId":17,"collectionName":18,"created":34,"created_by":13,"id":35,"name":36,"updated":37,"updated_by":13},"2026-03-04 08:51:08.805Z","75w01ebjq7zfrpt","GraphQL File Upload","2026-04-10 16:14:33.255Z",{"collectionId":17,"collectionName":18,"created":39,"created_by":13,"id":40,"name":41,"updated":42,"updated_by":13},"2026-03-04 08:51:09.476Z","l1ipjxpi9rr2jgh","WebSocket File Sharing","2026-04-10 16:14:33.437Z",{"collectionId":17,"collectionName":18,"created":44,"created_by":13,"id":45,"name":46,"updated":47,"updated_by":13},"2026-03-04 08:47:05.949Z","caufix9o52uw4bh","Real-Time Chat","2026-04-10 16:13:23.517Z",{"collectionId":17,"collectionName":18,"created":49,"created_by":13,"id":50,"name":51,"updated":52,"updated_by":13},"2026-03-04 08:20:14.253Z","ah6lvy4x8qe08l5","Golang","2026-04-10 16:07:26.172Z",{"collectionId":17,"collectionName":18,"created":54,"created_by":13,"id":55,"name":56,"updated":57,"updated_by":13},"2026-03-04 08:20:11.547Z","ey3puyme01a9bsw","Go","2026-04-10 16:07:25.893Z",{"collectionId":17,"collectionName":18,"created":59,"created_by":13,"id":60,"name":61,"updated":62,"updated_by":13},"2026-03-04 08:34:00.920Z","ecac9y661or1xka","WebSocket","2026-04-10 16:08:05.227Z",{"collectionId":17,"collectionName":18,"created":64,"created_by":13,"id":65,"name":66,"updated":67,"updated_by":13},"2026-03-04 08:51:09.841Z","6hrhrxemlcwn5fx","File Upload","2026-04-10 16:14:33.529Z",{"code":69,"collectionId":70,"collectionName":71,"created":72,"flag":73,"id":74,"is_default":75,"label":76,"updated":77},"th","pbc_1989393366","locales","2026-01-22 10:59:55.832Z","twemoji:flag-thailand","s8wri3bt4vgg2ji",true,"Thai","2026-04-10 15:42:46.614Z",{"category":79,"collectionId":80,"collectionName":81,"created":13,"expand":82,"id":96,"slug":97,"updated":98,"views":99},"wqxt7ag2gn7xcmk","pbc_2105096300","school_blogs",{"category":83},{"blogIds":84,"collectionId":85,"collectionName":86,"created":87,"created_by":13,"id":79,"image":88,"image_alt":13,"image_path":89,"label":90,"name":91,"priority":92,"publish_at":93,"scheduled_at":13,"status":94,"updated":95,"updated_by":13},[],"sclcatblg987654321","school_category_blogs","2026-03-04 08:33:53.210Z","59ty92ns80w_15oc1implw.png","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclcatblg987654321\u002Fwqxt7ag2gn7xcmk\u002F59ty92ns80w_15oc1implw.png",{"en":91,"th":91},"Golang The Series",1,"2026-03-16 04:39:38.440Z","published","2026-04-25 02:32:15.470Z","fwc905ic8t3yry7","file-upload-websocket-chat","2026-05-11 19:09:29.571Z",298,"jcsygzeq0ejxspb",[20,25,30,35,40,45,50,55,60,65],"2025-03-17 02:13:24.978Z","เรียนรู้วิธีเพิ่ม ฟีเจอร์การอัปโหลดไฟล์ ใน WebSocket Chat โดยใช้ Go และ GraphQL รองรับการแชร์ไฟล์ผ่าน WebSocket และจัดเก็บไฟล์ลงเซิร์ฟเวอร์หรือระบบ Cloud Storage เช่น AWS S3 หรือ Firebase Storage","2026-04-22 07:11:44.423Z",{"th":97,"en":97}]