Xây Dựng API với Query Parameters trong Go
Trong chương trước, chúng ta đã xây dựng một hệ thống định tuyến động có thể xử lý các tham số URL như /users/:id. Tuy nhiên, các API thực tế cần nhiều hơn thế - chúng cần xử lý các tham số truy vấn để lọc dữ liệu, phân trang và nhận đầu vào từ người dùng.
Hôm nay, chúng ta sẽ tìm hiểu cách làm việc với các tham số truy vấn để xây dựng các API tương tác hơn.
Những gì chúng ta đã xây dựng cho đến nay
Từ những chương trước:
- ✅ Định tuyến động với tham số URL (
/users/:id) - ✅ Trích xuất và truy cập tham số
- ✅ Phản hồi JSON cơ bản
- ❌ Xử lý tham số truy vấn (
?name=John&age=25) - ❌ Xác thực tham số và chuyển đổi kiểu dữ liệu
Hiểu về Tham số Truy vấn
Khi các lập trình viên frontend thực hiện các yêu cầu như sau:
// Tìm kiếm với tham số truy vấn
fetch('/search?q=javascript&page=2')
// Chào mừng với tên tùy chọn
fetch('/greet?name=John')
// Các phép toán toán học với tham số
fetch('/math/add?a=10&b=20')
Máy chủ cần trích xuất và sử dụng các tham số truy vấn đó. Khác với tham số URL (:id), tham số truy vấn xuất hiện sau dấu ? và được truyền dưới dạng cặp khóa-giá trị.
Trích xuất Tham số Truy vấn Cơ bản
Bắt đầu với một bộ xử lý chào mừng sử dụng tham số truy vấn. Cập nhật tệp main.go:
go
package main
import (
"fmt"
"net/http"
)
func main() {
server := NewServer(":3000")
setupRoutes(server)
server.Start()
}
func setupRoutes(s *Server) {
s.Router.GET("/greet", greet)
}
func greet(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
name := r.URL.Query().Get("name")
if name == "" {
w.Write([]byte("Xin chào người lạ!"))
return
}
w.Write([]byte(fmt.Sprintf("Xin chào %s", name)))
}
Cách Truy cập Tham số Truy vấn:
r.URL.Query()trả về tất cả các tham số truy vấn dưới dạng kiểuurl.ValuesGet("name")trích xuất giá trị cho tham sốname- Nếu tham số không tồn tại,
Get()sẽ trả về chuỗi rỗng
Kiểm tra bộ xử lý này:
go run .
curl "http://localhost:3000/greet"
# Trả về: Xin chào người lạ!
curl "http://localhost:3000/greet?name=John"
# Trả về: Xin chào John
curl "http://localhost:3000/greet?name=Jane%20Doe"
# Trả về: Xin chào Jane Doe
Lưu ý cách mã hóa URL hoạt động - %20 trở thành một khoảng trắng.
Nhiều Tham số Truy vấn
Các API thực tế thường cần nhiều tham số. Hãy tạo một bộ xử lý tìm kiếm:
go
func setupRoutes(s *Server) {
s.Router.GET("/greet", greet)
s.Router.GET("/search", search)
}
func search(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
query := r.URL.Query().Get("q")
page := r.URL.Query().Get("page")
w.Write([]byte(fmt.Sprintf("Kết quả tìm kiếm cho '%s' (trang %s)", query, page)))
}
Kiểm tra với nhiều tham số:
curl "http://localhost:3000/search?q=golang"
# Trả về: Kết quả tìm kiếm cho 'golang' (trang )
curl "http://localhost:3000/search?q=golang&page=3"
# Trả về: Kết quả tìm kiếm cho 'golang' (trang 3)
curl "http://localhost:3000/search?page=2&q=web%20development"
# Trả về: Kết quả tìm kiếm cho 'web development' (trang 2)
Thông tin Chính:
- Thứ tự tham số trong URL không quan trọng
- Tham số thiếu sẽ trả về chuỗi rỗng
- Tham số rỗng hiển thị dưới dạng trắng trong đầu ra
Chuyển đổi Kiểu Tham số
Thường thì bạn cần chuyển đổi các tham số truy vấn sang các kiểu cụ thể và xác thực chúng. Hãy tạo một bộ xử lý toán học:
go
func setupRoutes(s *Server) {
s.Router.GET("/greet", greet)
s.Router.GET("/search", search)
s.Router.GET("/math/add", add)
}
func add(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
a, _ := strconv.ParseInt(r.URL.Query().Get("a"), 0, 0)
b, _ := strconv.ParseInt(r.URL.Query().Get("b"), 0, 0)
w.Write([]byte(fmt.Sprintf("%d + %d = %d", a, b, a+b)))
}
Kiểm tra bộ xử lý toán học với các đầu vào khác nhau:
curl "http://localhost:3000/math/add?a=10&b=20"
# Trả về: 10 + 20 = 30
curl "http://localhost:3000/math/add?a=100&b=-50"
# Trả về: 100 + -50 = 50
curl "http://localhost:3000/math/add?a=hello&b=20"
# Trả về: 0 + 20 = 20
curl "http://localhost:3000/math/add?a=10"
# Trả về: 10 + 0 = 10
Hiểu về Các Kiểu Tham số Khác nhau
Hãy phân tích các cách khác nhau mà khách hàng có thể gửi dữ liệu tới API của bạn:
1. Tham số URL (Tham số Đường dẫn)
/users/123 → :id = "123"
/api/v1/users/456 → :version = "v1", :id = "456"
- Phần của đường dẫn URL
- Bắt buộc (URL sẽ không khớp nếu thiếu)
- Trích xuất bằng hàm
GetPathValue()
2. Tham số Truy vấn
/search?q=golang&page=2 → q = "golang", page = "2"
- Sau dấu
?trong URL - Tùy chọn (URL vẫn khớp nếu thiếu)
- Trích xuất bằng
r.URL.Query().Get()
3. Sử dụng Kết hợp
/users/123/posts?limit=10&sort=date
:idtừ đường dẫn = "123"limittừ truy vấn = "10"sorttừ truy vấn = "date"
Các Mô Hình Tham số Truy vấn Thông dụng
Mô Hình 1: Tham số Tùy chọn với Giá trị Mặc định
go
func listUsers(w http.ResponseWriter, r *http.Request) {
// Lấy tham số với giá trị mặc định
limitStr := r.URL.Query().Get("limit")
if limitStr == "" {
limitStr = "10" // Giá trị mặc định
}
sortBy := r.URL.Query().Get("sort")
if sortBy == "" {
sortBy = "name" // Sắp xếp mặc định
}
// Sử dụng các tham số...
}
Mô Hình 2: Tham số Boolean
go
func listProducts(w http.ResponseWriter, r *http.Request) {
// Tham số boolean
inStock := r.URL.Query().Get("in_stock") == "true"
onSale := r.URL.Query().Get("on_sale") == "true"
// Sử dụng cờ boolean...
}
Mô Hình 3: Nhiều Giá trị cho cùng một Tham số
go
func filterData(w http.ResponseWriter, r *http.Request) {
// Lấy tất cả giá trị cho một tham số
tags := r.URL.Query()["tags"] // []string
categories := r.URL.Query()["category"] // []string
// Xử lý nhiều giá trị...
}
Kiểm tra URL: /filter?tags=web&tags=api&category=tutorial&category=golang
So sánh Cách Tiếp cận của Chúng Ta với Các Framework
Cách Tiếp cận của Chúng Ta:
go
func handler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
id := GetPathValue(r, "id")
}
Framework Gin:
go
func handler(c *gin.Context) {
name := c.Query("name")
id := c.Param("id")
}
Framework Echo:
go
func handler(c echo.Context) error {
name := c.QueryParam("name")
id := c.Param("id")
}
Các khái niệm là giống nhau - các framework chỉ cung cấp các phương thức tiện lợi!
Triển Khai Hiện Tại
Các bộ xử lý của chúng ta thể hiện việc xử lý tham số truy vấn cơ bản với các mẫu khác nhau:
- Trích xuất tham số đơn giản bằng
r.URL.Query().Get() - Xử lý nhiều tham số trong một bộ xử lý
- Chuyển đổi kiểu sử dụng
strconv.ParseInt()
Kiểm Tra Tất Cả Các Bộ Xử Lý
Hãy kiểm tra bộ sưu tập bộ xử lý hoàn chỉnh của chúng ta:
# Kiểm tra bộ xử lý chào mừng
curl "http://localhost:3000/greet?name=Alice"
# Kiểm tra bộ xử lý tìm kiếm
curl "http://localhost:3000/search?q=golang%20tutorial&page=1"
# Kiểm tra bộ xử lý toán học
curl "http://localhost:3000/math/add?a=15&b=25"
# Kiểm tra với các số không hợp lệ (chúng trở thành 0)
curl "http://localhost:3000/math/add?a=not_a_number&b=10"
Những gì chúng ta đã đạt được
Chúng ta đã có:
- ✅ Trích xuất tham số truy vấn (
r.URL.Query().Get()) - ✅ Xử lý nhiều tham số (tìm kiếm với tham số truy vấn + trang)
- ✅ Chuyển đổi kiểu (chuỗi thành số nguyên với
strconv.ParseInt()) - ✅ Các phép toán cơ bản với tham số URL
Điều gì tiếp theo?
Trong chương 6, chúng ta sẽ mở rộng khả năng của máy chủ:
- Lưu trữ Dữ liệu: Làm việc với lưu trữ dữ liệu có cấu trúc
- API JSON: Tạo xử lý yêu cầu/phản hồi JSON đúng cách
- Xử lý Thân yêu cầu: Xử lý yêu cầu POST/PUT với dữ liệu JSON
- Hoạt động CRUD: Xây dựng một API hoàn chỉnh để quản lý tài nguyên
Thử thách: Hãy thử tạo một điểm cuối /calculator mà chấp nhận các tham số operation (cộng, trừ, nhân, chia), a, và b. Xử lý chia cho số 0 và các phép toán không hợp lệ!
Thưởng thêm: Thêm hỗ trợ cho nhiều giá trị: /tags?name=web&name=api&name=tutorial và trả về tất cả các tên thẻ.