การอัพโหลดไฟล์บนเว็บไซต์ โดยใช้ Angular และ Golang

By KDBEER | Last updated Jul 9, 2022
การอัพโหลดไฟล์บนเว็บไซต์-โดยใช้-Angular-และ-Golang-5fd284e104fa450b9d1ef244

ผมเจอคำถามในกลุ่ม Programmer หลายๆ แห่งครับ คำถามก็คือ จะเขียนโปรแกรม เพื่อให้สามารถอัปโหลดไฟล์ซึ่งเป็น web application ซึ่งพัฒนาโดยใช้ Angular ได้อย่างไร บทความนี้ วิธีที่ผมจะนำเสนอ ก็คือการใช้ Angular เป็น Frontend และใช้ Golang เป็น Backend ครับ เอาละครับ มาเริ่มกันเลยครับ 

Note ตัวอย่างจากบทความนี้รันอยู่บน localhost ดังนี้ การจะเอาไปใช้จริงบน production ก็จะต่างจากนี้อยู่พอสมควรนะครับ ถือว่าเอาไว้เป็นไอเดียละกันครับ

 

Http Server

เราจะเริ่มจากการสร้าง http server เพื่อใช้รับ request จาก angular ครับ ซึ่งในตัวอย่างนี้ ผมจะสร้าง endpoint ที่ชื่อ ‘/upload’ เอาไว้อัพโหลดไฟล์ และผมจะรัน service บน port 3000 ครับ

ก่อนอื่นเลย คือสร้าง folder เพื่อเก็บ source code ก่อนครับ

mkdir go-upload-example
cd go-upload-example

จากนั้นสร้างไฟล์ main.go และเขียนโค๊ดตามนี้ครับ

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

const serverPort = 3000
const uploadFileDirectory = "uploads"

// IResponse response interface
type IResponse struct {
	Message string `json:"message"`
}

func jsonResponse(w http.ResponseWriter, data interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(data)
}

func uploadFileHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Println("[uploadFileHandler] start working")

	// Allow upload fron cross origin
	w.Header().Set("Content-Type", "text/html; charset=ascii")
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type,access-control-allow-origin, access-control-allow-headers")

	// FormFile returns the first file for the given key `myFile`
	// it also returns the FileHeader so we can get the Filename,
	// the Header and the size of the file
	file, handler, err := r.FormFile("file")
	if err != nil {
		jsonResponse(w, IResponse{Message: "failed"})
		return
	}
	defer file.Close()
	fmt.Printf("Uploaded File: %+v\n", handler.Filename)
	fmt.Printf("File Size: %+v\n", handler.Size)
	fmt.Printf("MIME Header: %+v\n", handler.Header)

	// Create a temporary file within our temp-images directory that follows
	// a particular naming pattern
	tempFile, err := ioutil.TempFile(uploadFileDirectory, "upload-*.png")
	if err != nil {
		fmt.Println(err)
	}
	defer tempFile.Close()

	// read all of the contents of our uploaded file into a
	// byte array
	fileBytes, err := ioutil.ReadAll(file)
	if err != nil {
		jsonResponse(w, IResponse{Message: "failed"})
		return
	}
	// write this byte array to our temporary file
	tempFile.Write(fileBytes)

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	jsonResponse(w, IResponse{Message: "success"})
}

func main() {
	http.HandleFunc("/upload", uploadFileHandler)

	fmt.Println("Server started at port : ", serverPort)
	http.ListenAndServe(fmt.Sprintf(":%d", serverPort), nil)
}

Method `uploadFileHandler` จะรับ request ที่รับมาเป็น Form Data ครับ เมื่ออ่านข้อมูลได้แล้ว ระบบจะบันทึกไฟล์ไว้ที่โฟลเดอร์ที่ชื่อว่า uploads ครับ ดังนั้นเราต้องสร้างโฟลเดอร์ไว้เก็บไฟล์ ดังนี้ครับ

mkdir uploads

จากนั้น จะ run service โดยใช้คำสั่งนี้ครับ

go run main.go

ถ้าไม่มีอะไรผิดพลาด เราจะเห็นผลลัพธ์จากการรันแบบนี้ http server แบบนี้ ก็แปลว่า server พร้อมใช้งานแล้วครับ

Server started at port :  3000

 

Angular Frontend

หลังจากที่ web service พร้อมแล้ว จากขั้นตอนที่ 1 อันดับต่อไป ผมจะสร้างเว็บไซต์ที่ใช้ในการอัปโหลดไฟล์ไปที่ web service ครับ โดยเริ่มจากการสร้างเว็บไซต์โดยใช้ angular โดยใช้คำสั่งนี้ครับ

ng new ng-upload-example

ที่ app component ผมจะทำการแก้ไขไฟล์ ดังนี้ครับ

app.component.html

<div class="container">
  <form class="upload">
    <div class="file-container">
      <input class="file" type="file" (change)="onFileChanged($event)" />
      <img src="https://simpleicon.com/wp-content/uploads/cloud-upload-2.svg" />
    </div>

    <div class="detail" *ngIf="file">
      {{ file.name }}
    </div>

    <div class="detail" *ngIf="!file">No file selected</div>

    <div class="message" *ngIf="message !== ''">
      {{ message }}
    </div>

    <div class="buttons">
      <input
        type="button"
        value="upload"
        [disabled]="!file"
        (click)="uploadFile()"
      />
    </div>
  </form>
</div>

app.component.ts

import { AppService } from './../services/app.service';
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  title = 'ng-upload-example';

  file: any;
  message = '';

  constructor(private as: AppService) {}

  onFileChanged(event: any) {
    if (!event) {
      return;
    }

    if (event.target.files.length <= 0) {
      return;
    }

    this.file = event.target.files[0];
  }

  uploadFile() {
    const fd = new FormData();
    fd.append('file', this.file);
    this.message = '';

    this.as.uploadFile(fd).subscribe(
      (res) => {
        this.message = res + '';
      },
      (err) => {
        console.log(err);
        this.message = 'Failed';
      }
    );
  }
}

จากนั้น ปรับแต่ style ซักหน่อยครับ

app.component.scss

.container {
  width: 100%;
  height: 100%;
}

.upload {
  width: 500px;
  padding: 50px;
  margin: 100px auto;
  border: 1px solid #eeeeee;
  text-align: center;
}

.buttons,
.detail {
  margin-top: 20px;
}

.file-container {
  width: 150px;
  height: 150px;
  border: 1px dashed #cccccc;
  margin: 0 auto;
  position: relative;
}

.file-container .file {
  z-index: 1;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  cursor: pointer;
  opacity: 0;
}

.file-container img {
  top: 50%;
  left: 50%;
  width: 80px;
  position: absolute;
  transform: translate(-50%, -50%);
  z-index: 0;
}

เอาละครับ ในส่วนของ app component ก็เสร็จแล้ว ถ้าดูไฟล์ app.component.ts จะเห็นว่า มันเรียกไปหา AppService เพื่อใช้เชื่อมต่อ API เราจะสร้างไฟล์นั้นต่อครับ โดยใช้คำสั่งต่อไปนี้ครับ เพื่อสร้าง service ครับ

ng g s app

จากนั้น แก้ไขไฟล์ service.ts ดังนี้ครับ

app.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class AppService {
  api = 'http://localhost:3000/';
  constructor(private http: HttpClient) {}

  uploadFile(fd: FormData) {
    return this.http.post(this.api + 'upload', fd);
  }
}

อันดับสุดท้าย แก้ไฟล์ app.module ดังนี้ครับ เพื่อให้ angular มัน import http client module มาใช้เชื่อมต่อ api ครับ

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AppRoutingModule, HttpClientModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

 

ทดสอบ

เอาละครับ เราได้สร้างทั้ง Frontend และ Backend เรียบร้อยแล้ว ทีนี้เรามาทดสอบกันครับ โดยใช้คำสั่ง

ng serve

เมื่อรันเสร็จแล้ว จะได้ผลลัพธ์แบบนี้ครับ แล้วลองกดทดสอบดู ก็ควรจะเห็นผลลัพธ์แบบนี้ครับ

 

เย้!! เป็นอันว่าเสร็จเรียบร้อยแล้วนะครับ การเขียนอัพโหลดไฟล์ด้วย Angular คราวหลังจะมีบทความใดมาฝากอีก ก็แวะมาเยี่ยม blog ของผมได้นะครับ ขอบคุณครับ