Golang: Form validations

Như title của bài viết, nội dung của bài viết này sẽ hướng dẫn các bạn các sử dụng Golang để xây dựng một form contact. Chúng ta sẽ đi từng bước từng bước từ khởi đầu cho tới khi kết thúc.

Hãy bắt đầu với việc tạo thư mục cho ứng dụng của chúng ta:

$ mkdir -p viblo/templates
$ cd viblo
$ touch main.go templates/index.html templates/confirmation.html

Sau khi hoàn tất bước trên, mở file templates/index.html bằng công cụ code ưa thích của bạn, thêm vào file này nội dung sau:

<h1>Contact</h1>
<form action="/" method="POST" novalidate>
  <div>
    <label>Your email:</label>
    <input type="email" name="email">
  </div>
  <div>
    <label>Your message:</label>
    <textarea name="content"></textarea>
  </div>
  <div>
    <input type="submit" value="Send message">
  </div>
</form>

Tiếp đến là file templates/confirmation.html với nội dung

<h1>Confirmation</h1>
<p>Your message has been sent!</p>

Mẫu form này sẽ yêu cầu 1 request có method là POST tới /. Điều này có nghĩa là chúng ta cần định nghĩa router cho cùng một URL nhưng xử lý khác nhau dựa trên các phương thức của HTTP. Trong Golang có rất nhiều cách để xử lý hướng đi này. Nhưng trong bài viết này tôi sẽ gợi ý cho các bạn sử dụng package Pat. Trước hết, để sử dụng được package này thì chúng ta cần tiến hành cài đặt nó vào root của golang trên thiết bị của mình.

$ go get github.com/bmizerany/pat

Bây giờ sẽ là phần main của ứng dụng. Mở file main.go và thêm vào nội dung sau:

package main

import (
  "github.com/bmizerany/pat"
  "html/template"
  "log"
  "net/http"
)

func main() {
  mux := pat.New()
  mux.Get("/", http.HandlerFunc(index))
  mux.Post("/", http.HandlerFunc(send))
  mux.Get("/confirmation", http.HandlerFunc(confirmation))

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

func index(w http.ResponseWriter, r *http.Request) {
  render(w, "templates/index.html", nil)
}

func send(w http.ResponseWriter, r *http.Request) {
  // Validate form
  // Send message in an email
  // Redirect to confirmation page
}

func confirmation(w http.ResponseWriter, r *http.Request) {
  render(w, "templates/confirmation.html", nil)
}

func render(w http.ResponseWriter, filename string, data interface{}) {
  tmpl, err := template.ParseFiles(filename)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  if err := tmpl.Execute(w, data); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
}

Sau khi hoàn tất, tiến hành chạy lệnh:

$ go run main.go

Và hãy mở trình duyệt của bạn lên, truy cập vào địa chỉ http://localhost:3000/ bạn sẽ thấy contact form mà chúng ta đã tạo trong file inex.html. Tiếp theo đây mới là phần thú vị nhất của bài viết này. Hãy thêm một vài quy tắc validation vào cho form của chúng ta. Chúng ta sẽ hiển thị thông báo lỗi nếu người dùng nhập sai một số điều kiện và các giá trị nhập này sẽ được giữ lại để người dùng không cần phải nhập lại nó. Hướng giải quyết của vấn đề này là chúng ta sẽ thêm code bên trong function send của file main.go. Để code của chúng ta sạch sẽ, dễ nhìn hãy viết bước xử lý này ra một file message.go. Hãy tạo file message.go bằng lệnh:

$ touch message.go

Mở file này lên và thêm nội dụng như sau:

package main

import (
 "regexp"
 "strings"
)

type Message struct {
 Email    string
 Content string
 Errors  map[string]string
}

func (msg *Message) Validate() bool {
 msg.Errors = make(map[string]string)

 re := regexp.MustCompile("[email protected]+\\..+")
 matched := re.Match([]byte(msg.Email))
 if matched == false {
   msg.Errors["Email"] = "Please enter a valid email address"
 }

 if strings.TrimSpace(msg.Content) == "" {
   msg.Errors["Content"] = "Please write a message"
 }

 return len(msg.Errors) == 0
}

Giải thích qua chút nào:

Đầu tiên chúng ta định nghĩa một struct mới có tên là Message. Struct này bao gồm các thành phần là Email, Content và Errors. Email và Content dùng để lưu lại giá trị email và content khi form được submit. Và mảng errors để lưu lại giá trị errors khi có lỗi xảy ra trong quá tình submit form. Tiếp theo, tạo một mehthod là Validate() nó hoạt động cho một struct Message nhất định. Trong hàm validate này tiến hành kiểm tra một email nhập vào form có đúng định dạnh hay không? và yêu cầu người dùng phải nhập nội dung content vào form. Nếu Validate() faile chúng ta sẽ hiển thị lại form contact cho người dùng.

Hãy sửa lại func send() trong file main.go một chút như sau:

func send(w http.ResponseWriter, r *http.Request) {
 msg := &Message{
   Email: r.FormValue("email"),
   Content: r.FormValue("content"),
 }

 if msg.Validate() == false {
   render(w, "templates/index.html", msg)
   return
 }

 // Send message in an email
 // Redirect to confirmation page
}

Ở đây, tôi đã dùng phương thức r.FormValue() để lấy giá trị của phương thức POST form. Bạn cũng có thể lấy được những giá trị này theo cách sau:

err := r.ParseForm()
// Handle error
msg := &Message{
 Email: r.Form.Get("email"),
 Content: r.Form.Get("content"),
}

Cập nhật lại một chút file index.html là những lỗi nếu có sẽ được hiển thị ngay phía bên dưới các trường của form:

File: templates/index.html
<style type="text/css">.error {color: red;}</style>

<h1>Contact</h1>
<form action="/" method="POST" novalidate>
 <div>
   {{ with .Errors.Email }}
   <p class="error">{{ . }}</p>
   {{ end }}
   <label>Your email:</label>
   <input type="email" name="email" value="{{ .Email }}">
 </div>
 <div>
   {{ with .Errors.Content }}
   <p class="error" >{{ . }}</p>
   {{ end }}
   <label>Your message:</label>
   <textarea name="content">{{ .Content }}</textarea>
 </div>
 <div>
   <input type="submit" value="Send message">
 </div>
</form>

Công việc tới đây là hoàn tất. Hãy chạy lệnh sau và xem thành quả:

$ go run main.go message.go