Query Parameters (hay Query String) là một trong những khái niệm quan trọng nhất khi làm việc với API. Bài viết này sẽ giúp bạn hiểu rõ từ cơ bản đến nâng cao về cách sử dụng query parameters trong API testing.
Query Parameters là gì?
Query Parameters là cách gửi dữ liệu kèm theo URL, xuất hiện sau dấu ? trong URL.
Ví dụ:
https://api.example.com/users?age=25&city=hanoi&status=active
Phân tích cấu trúc:
?– Dấu bắt đầu query stringage=25– Parameter đầu tiên (key=value)&– Dấu phân tách giữa các parameterscity=hanoi– Parameter thứ haistatus=active– Parameter thứ ba
Cấu trúc và Syntax
Cú pháp cơ bản
// Một parameter
?key=value
// Nhiều parameters
?key1=value1&key2=value2&key3=value3
// Ví dụ thực tế
?name=john&age=30&city=hanoi
?status=active
?sort=name
?page=2&limit=20
?q=keyword
Làm việc với dữ liệu dạng mảng
Có nhiều cách để gửi dữ liệu dạng mảng, không có chuẩn chung:
// 1. PHP style (phổ biến nhất)
?tags[]=php&tags[]=api&tags[]=testing
// 2. Repeat key (Rails style)
?tags=php&tags=api&tags=testing
// 3. Comma-separated
?tags=php,api,testing
// 4. Brackets with index
?tags[0]=php&tags[1]=api&tags[2]=testing
Làm việc với dữ liệu dạng object
// Filter object
?filter[name]=john&filter[age]=30&filter[city]=hanoi
// Sort object
?sort[field]=created_at&sort[order]=desc
// Range object
?price[min]=100&price[max]=500
URL Encoding
Một số ký tự đặc biệt cần được encode:
| Ký tự | URL Encoded | Ví dụ |
|---|---|---|
| Space | %20 hoặc + | hello world → hello%20world |
| @ | %40 | user@example.com → user%40example.com |
| & | %26 | A&B → A%26B |
| = | %3D | x=y → x%3Dy |
| ? | %3F | what? → what%3F |
| # | %23 | #hashtag → %23hashtag |
| / | %2F | path/to → path%2Fto |
| Tiếng Việt | UTF-8 encoded | Hà Nội → H%C3%A0%20N%E1%BB%99i |
Ví dụ:
// ✗ Sai: Không encode
?email=user@example.com&name=Nguyễn Văn A&search=hello world
// ✓ Đúng: Encoded
?email=user%40example.com&name=Nguy%E1%BB%85n%20V%C4%83n%20A&search=hello%20world
Làm việc với dữ liệu dạng boolean và number
Lưu ý quan trọng:
- Server luôn nhận query parameters dưới dạng string
?age=25→ Server nhận"25"(string), không phải25(number)?active=true→ Server nhận"true"(string), không phảitrue(boolean)
Các cách biểu diễn boolean:
// String representation
?is_active=true
?verified=false
// Numeric representation (0/1)
?is_active=1
?verified=0
// Presence check (có parameter = true)
?is_active // Có parameter
// Không có parameter = false
Use Cases phổ biến
1. Filtering (Lọc dữ liệu)
Basic Filtering
// Lọc theo status
GET /api/orders?status=completed
// Lọc theo category
GET /api/products?category=laptop
// Lọc theo boolean
GET /api/users?is_active=true&verified=true
// Kết hợp nhiều điều kiện (AND)
GET /api/products?category=laptop&brand=dell&in_stock=true
Range Filtering
// Price range
GET /api/products?price_min=10000000&price_max=20000000
// Date range
GET /api/orders?date_from=2025-01-01&date_to=2025-01-31
Filter nâng cao với các toán tử
// age >= 18 AND age <= 65
GET /api/users?age[gte]=18&age[lte]=65
// price > 1000000 AND rating >= 4
GET /api/products?price[gt]=1000000&rating[gte]=4
// status IN ('published', 'draft')
GET /api/posts?status[in]=published,draft
// name LIKE '%nguyen%'
GET /api/users?name[like]=%nguyen%
Các Operators phổ biến:
| Operator | Ý nghĩa | Ví dụ |
|---|---|---|
eq | Bằng (equal) | ?age[eq]=25 |
ne | Không bằng (not equal) | ?status[ne]=deleted |
gt | Lớn hơn (greater than) | ?price[gt]=1000000 |
gte | Lớn hơn hoặc bằng | ?age[gte]=18 |
lt | Nhỏ hơn (less than) | ?quantity[lt]=10 |
lte | Nhỏ hơn hoặc bằng | ?age[lte]=65 |
in | Trong danh sách | ?status[in]=active,pending |
nin | Không trong danh sách | ?role[nin]=admin,superadmin |
like | Pattern matching | ?name[like]=%nguyen% |
between | Trong khoảng | ?price[between]=100,500 |
2. Sorting (Sắp xếp)
Basic Sorting
// Sort by name (ascending - mặc định)
GET /api/users?sort=name
// Sort by created date (descending)
GET /api/posts?sort=-created_at // Dấu "-" = descending
// Sort by multiple fields
GET /api/products?sort=category,price
Các style sorting phổ biến
// Style 1: Separate order parameter
GET /api/users?sort=name&order=asc
GET /api/users?sort=created_at&order=desc
// Style 2: orderBy và direction
GET /api/products?orderBy=price&direction=asc
// Style 3: sort object
GET /api/posts?sort[field]=created_at&sort[order]=desc
Multi-field Sorting
// Comma-separated
GET /api/products?sort=category,-price,name
// Sort by: category ASC, price DESC, name ASC
// Array style
GET /api/users?sort[]=created_at:desc&sort[]=name:asc
// Object style
GET /api/posts?sort[0][field]=published_at&sort[0][order]=desc&sort[1][field]=title&sort[1][order]=asc
So sánh các pattern sorting:
| Pattern | Ví dụ | Framework |
|---|---|---|
Prefix - | ?sort=-created_at | JSON:API, Rails |
| Separate order | ?sort=name&order=desc | Common |
| orderBy/direction | ?orderBy=price&direction=asc | Laravel |
| Colon separator | ?sort=name:asc | Custom |
| Object notation | ?sort[field]=name&sort[order]=asc | Custom |
3. Pagination (Phân trang)
Page-based Pagination
// Page và limit
GET /api/users?page=1&limit=20
GET /api/users?page=2&limit=20
// Page và per_page (Rails style)
GET /api/posts?page=3&per_page=50
// Page bắt đầu từ 0
GET /api/products?page=0&size=25
Offset-based Pagination
GET /api/users?offset=0&limit=20 // Records 1-20
GET /api/users?offset=20&limit=20 // Records 21-40
GET /api/users?offset=40&limit=20 // Records 41-60
// Skip và take
GET /api/products?skip=0&take=50
Cursor-based Pagination
// First page
GET /api/posts?limit=20
// Response includes cursor
{
"data": [...],
"next_cursor": "eyJpZCI6MTAwfQ=="
}
// Next page
GET /api/posts?limit=20&cursor=eyJpZCI6MTAwfQ==
So sánh các loại pagination:
| Type | Ưu điểm | Nhược điểm | Use case |
|---|---|---|---|
| Page-based | Đơn giản, dễ hiểu, có thể jump tới page bất kỳ | Kém performance với data lớn, vấn đề với data thay đổi | Admin panels, danh sách thông thường |
| Offset-based | Linh hoạt, có thể skip số lượng bất kỳ | Chậm với offset lớn, không consistent khi data thay đổi | Infinite scroll với số lượng giới hạn |
| Cursor-based | Performance tốt, consistent khi data thay đổi | Phức tạp, không thể jump tới page giữa | Social feeds, real-time data, infinite scroll |
Response format chuẩn:
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 157,
"total_pages": 8,
"has_next": true,
"has_prev": true
}
}
4. Search (Tìm kiếm)
Basic Search
// Single query parameter
GET /api/users?q=john
GET /api/products?search=laptop
GET /api/posts?query=api+testing
// Search in specific fields
GET /api/users?search=nguyen&fields=name,email
// Separate parameters cho từng field
GET /api/users?name=nguyen&email=gmail.com
Advanced Search
// Advanced search với operators
GET /api/products?name[like]=%laptop%&description[like]=%gaming%
// Full-text search
GET /api/posts?q=api+testing+tutorial
// With search mode
GET /api/articles?q=playwright&mode=fulltext
// With relevance
GET /api/search?q=javascript&min_score=0.5
Lưu ý về encoding trong search:
// Search có spaces
GET /api/posts?q=api+testing
GET /api/posts?q=api%20testing
// Search có quotes
GET /api/search?q="exact phrase"
GET /api/search?q=%22exact%20phrase%22
// Search có special characters
GET /api/users?email=user%40example.com
GET /api/search?q=C%23+programming
5. Field Selection
// Only return id, name, email
GET /api/users?fields=id,name,email
// Exclude password and token
GET /api/users?exclude=password,token
// Include related data
GET /api/posts?include=author,comments
// Select fields from related objects
GET /api/posts?fields=title,content&include=author&author_fields=name,email
6. Date Filtering
Date Range
// From and to
GET /api/orders?date_from=2025-01-01&date_to=2025-01-31
// Start and end
GET /api/logs?start_date=2025-01-01&end_date=2025-12-31
// After and before
GET /api/posts?created_after=2025-01-01&created_before=2025-02-01
// Specific field với operators
GET /api/orders?created_at[gte]=2025-01-01&created_at[lte]=2025-01-31
Date Formats
// ISO 8601 date (recommended)
?date=2025-01-15
// ISO 8601 datetime
?created_at=2025-01-15T10:30:00Z
// Unix timestamp
?timestamp=1736935800
// Relative dates
?date=today
?date=yesterday
?date=last_7_days
?date=this_month
Query Parameters và các lựa chọn thay thế
Query Parameters vs Path Parameters
| Khía cạnh | Query Parameters | Path Parameters |
|---|---|---|
| Syntax | /users?id=123 | /users/123 |
| Required | Optional | Required (part of route) |
| Use case | Filtering, sorting, options | Resource identification |
| Multiple values | ?tags=php&tags=api | Khó (cần nhiều segments) |
| SEO | Kém hơn | Tốt hơn |
Khi nào dùng gì:
// ✓ Path param: Identify specific resource
GET /api/users/123 // Get user với ID 123
GET /api/posts/456/comments/789 // Get comment 789 của post 456
// ✓ Query param: Optional filtering/options
GET /api/users?role=admin&active=true
GET /api/products?category=laptop&sort=price
// ✗ Không nên: Required param trong query
GET /api/users?id=123 // Nên dùng /users/123
// ✗ Không nên: Complex path
GET /api/filter/category/laptop/price/1000000/2000000 // Quá dài!
Query Parameters vs Request Body
| Khía cạnh | Query Parameters | Request Body |
|---|---|---|
| HTTP Methods | GET, DELETE | POST, PUT, PATCH |
| Size limit | ~2KB (URL length limit) | Lớn hơn nhiều (MB+) |
| Cacheable | Yes | No (thường) |
| Visible in URL | Yes | No |
| Bookmarkable | Yes | No |
| Security | Logged, visible | More secure |
Khi nào dùng gì:
// ✓ Query params: GET với simple filters
GET /api/products?category=laptop&price_max=20000000
// ✓ Body: POST/PUT với complex data
POST /api/users
Body: {
"name": "Nguyễn Văn A",
"email": "vana@example.com",
"address": {
"street": "123 Main St",
"city": "Hanoi"
}
}
// ✓ Body: Complex search với many conditions
POST /api/search
Body: {
"filters": [...],
"sort": [...],
"page": 1,
"limit": 20
}
// ✗ Không nên: Sensitive data trong query
GET /api/login?email=user@example.com&password=123456 // Nguy hiểm!
// ✓ Đúng: Dùng POST body
POST /api/login
Body: {
"email": "user@example.com",
"password": "123456"
}
Decision Tree: Chọn phương thức phù hợp
Is it required to identify the resource?
→ YES: Use PATH parameter (/users/123)
→ NO: Continue...
Is it sensitive data (password, token)?
→ YES: Use REQUEST BODY (POST) or HEADER (Authorization)
→ NO: Continue...
Is it large/complex data?
→ YES: Use REQUEST BODY
→ NO: Continue...
Is it metadata about the request?
→ YES: Use HEADER (Accept, Content-Type, ...)
→ NO: Continue...
Is it filtering/sorting/pagination?
→ YES: Use QUERY PARAMETERS
→ DEFAULT: Use QUERY PARAMETERS
Best Practices
1. Naming Conventions
Chọn một style và giữ nhất quán:
// ✓ snake_case
?first_name=john&last_name=doe
?created_at=2025-01-01
?is_active=true
?price_min=100&price_max=500
// ✓ camelCase
?firstName=john&lastName=doe
?createdAt=2025-01-01
?isActive=true
?priceMin=100&priceMax=500
// ✓ Consistent (tất cả snake_case)
?first_name=john&last_name=doe&is_active=true
// ✗ Inconsistent (mixed)
?firstName=john&last_name=doe&isActive=true
2. Security Considerations
SQL Injection Prevention
// ✗ Nguy hiểm: SQL Injection
const userId = req.query.id;
db.query(`SELECT * FROM users WHERE id = ${userId}`);
// ✓ An toàn: Parameterized query
const userId = parseInt(req.query.id);
db.query('SELECT * FROM users WHERE id = ?', [userId]);
Không gửi Sensitive Data trong Query
// ✗ Nguy hiểm
GET /api/reset-password?email=user@example.com&new_password=123456
// ✓ An toàn
POST /api/reset-password
Body: {
"email": "user@example.com",
"new_password": "123456"
}
XSS Prevention
// ✗ Nguy hiểm
const search = req.query.q;
res.send(`<h1>Search results for: ${search}</h1>`);
// Nếu q = "<script>alert('xss')</script>"
// ✓ An toàn: Escape HTML
const search = escapeHtml(req.query.q);
res.send(`<h1>Search results for: ${search}</h1>`);
Rate Limiting
// Prevent abuse của search/filter endpoints
GET /api/search?q=a // Quá ngắn
GET /api/search?q=aaaaaaa... // Quá dài
GET /api/users?limit=999999 // Quá nhiều
// Server nên:
// - Minimum search length (>= 2-3 chars)
// - Maximum search length (<= 100 chars)
// - Maximum limit (<= 100)
// - Rate limit (max X requests per minute)
3. Validation
Server-side Validation
// Server code example
const page = parseInt(req.query.page) || 1;
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
const sort = req.query.sort || 'created_at';
const order = req.query.order || 'desc';
// Validate pagination
if (page < 1) page = 1;
if (limit < 1) limit = 20;
if (limit > 100) limit = 100; // Max limit
// Validate date range
if (dateFrom > dateTo) {
return error("date_from must be before date_to");
}
const maxDays = 365;
if (dateTo - dateFrom > maxDays * 24 * 60 * 60 * 1000) {
return error("Date range cannot exceed 1 year");
}
4. Caching Friendly
// ✓ Good: Consistent parameter order (cache-friendly)
/api/products?category=laptop&sort=price&page=1
/api/products?category=laptop&sort=price&page=2
// ✗ Bad: Random order (different cache keys)
/api/products?sort=price&category=laptop&page=1
/api/products?page=1&category=laptop&sort=price
5. Ngắn gọn và rõ ràng
// ✓ Clear
?search=laptop
?category=electronics
?price_min=1000000
?sort=price&order=asc
// ✗ Unclear
?s=laptop
?cat=e
?pmin=1000000
?sb=p&so=a
6. Luôn có giá trị default
// Default values make sense
GET /api/products
// Defaults: page=1, limit=20, sort=created_at, order=desc
GET /api/users
// Defaults: active=true, page=1, limit=50
7. Error Messages rõ ràng
// ✓ Good error message
{
"error": "Invalid parameter",
"message": "Parameter 'limit' must be between 1 and 100. You provided: 999",
"field": "limit",
"provided_value": 999,
"valid_range": [1, 100]
}
// ✗ Bad error message
{
"error": "Bad request"
}
8. Documentation đầy đủ
/**
* GET /api/products
*
* Query Parameters:
* - category (string, optional): Product category
* Example: "laptop", "phone"
*
* - price_min (number, optional): Minimum price
* Range: 0 - 999999999
* Default: 0
*
* - price_max (number, optional): Maximum price
* Range: 0 - 999999999
* Default: 999999999
*
* - sort (string, optional): Sort field
* Valid values: "price", "name", "created_at", "rating"
* Default: "created_at"
*
* - order (string, optional): Sort order
* Valid values: "asc", "desc"
* Default: "desc"
*
* - page (number, optional): Page number
* Range: 1 - 9999
* Default: 1
*
* - limit (number, optional): Items per page
* Range: 1 - 100
* Default: 20
*
* Example:
* GET /api/products?category=laptop&price_max=20000000&sort=price&order=asc&page=1&limit=20
*/
Common Issues & Solutions
1. URL quá dài
Problem: URLs quá dài bị reject hoặc truncate
// URL quá dài (~10KB)
GET /api/products?id=1&id=2&id=3&id=4&id=5&...&id=1000
Limits:
- Browsers: ~2KB – 8KB
- Servers: ~8KB – 16KB (configurable)
Solutions:
// ✓ Solution 1: Dùng POST với body
POST /api/products/bulk-get
Body: {
"ids": [1, 2, 3, ..., 1000]
}
// ✓ Solution 2: Pagination
GET /api/products?page=1&limit=100
GET /api/products?page=2&limit=100
// ✓ Solution 3: Shorten parameter names
?ids=1,2,3,4,5 // Thay vì ?id=1&id=2&id=3...
2. Special Characters không encode
Problem: Special characters không được encode đúng
// ✗ Broken
GET /api/search?q=A&B // "&B" bị hiểu là parameter khác!
GET /api/users?email=user@example.com // "@" có thể gây lỗi
Solution:
// ✓ Encode properly
GET /api/search?q=A%26B // %26 = &
GET /api/users?email=user%40example.com // %40 = @
// ✓ Playwright auto-encodes
const response = await request.get('/api/search', {
params: {
q: 'A&B', // Tự động encode thành A%26B
email: 'user@example.com' // Tự động encode
}
});
3. Array Parameters không nhất quán
Problem: Không có chuẩn chung cho array parameters
// PHP style
?tags[]=php&tags[]=api
// Rails style / Standard
?tags=php&tags=api
// Comma-separated
?tags=php,api
// JSON
?tags=["php","api"] // Cần encode
Solution: Document rõ ràng format nào API hỗ trợ
/**
* tags parameter accepts multiple values in these formats:
* 1. Comma-separated: ?tags=php,api,testing (recommended)
* 2. Repeated keys: ?tags=php&tags=api&tags=testing
*/
4. Type Conversion
Problem: Query params luôn là strings, cần convert
// Client gửi
?age=25&active=true&price=10.5
// Server nhận (tất cả là strings!)
{
age: "25", // String, không phải number!
active: "true", // String, không phải boolean!
price: "10.5" // String, không phải number!
}
Solution: Parse và validate
// Server-side parsing
const age = parseInt(req.query.age);
const active = req.query.active === 'true';
const price = parseFloat(req.query.price);
// With validation
const age = parseInt(req.query.age);
if (isNaN(age) || age < 0 || age > 150) {
return error("Invalid age");
}
5. Unicode Characters
Problem: Unicode characters (tiếng Việt, emoji) không encode đúng
// ✗ Broken
?name=Nguyễn Văn A
?search=
Solution:
// ✓ URL encode UTF-8
?name=Nguy%E1%BB%85n%20V%C4%83n%20A
?search=%F0%9F%98%80
// ✓ Playwright handles automatically
const response = await request.get('/api/users', {
params: {
name: 'Nguyễn Văn A', // Auto UTF-8 encode
search: '' // Auto encode emoji
}
});
6. Parameter Pollution
Problem: Duplicate parameters với ý đồ xấu
// Attacker gửi
GET /api/transfer?amount=1&amount=99999
// Server chỉ lấy giá trị đầu (amount=1) để validate
// Nhưng lấy giá trị sau (amount=99999) để thực hiện
// → Bypass validation!
Solution:
// ✓ Reject duplicate params (nếu không hỗ trợ arrays)
if (req.query.amount && Array.isArray(req.query.amount)) {
return error("Multiple 'amount' parameters not allowed");
}
// ✓ Hoặc chỉ lấy giá trị đầu tiên và validate
const amount = Array.isArray(req.query.amount)
? req.query.amount[0]
: req.query.amount;
Tổng kết
Query Parameters là một công cụ mạnh mẽ trong API testing. Hiểu rõ cách sử dụng chúng sẽ giúp bạn:
- Testing hiệu quả hơn – Test filtering, sorting, pagination một cách chuyên nghiệp
- Tránh được các lỗi phổ biến – URL encoding, type conversion, security issues
- Viết code clean hơn – Sử dụng đúng cách với Playwright và các framework khác
- Hiểu API design tốt hơn – Biết khi nào dùng query params, khi nào dùng alternatives
Các điểm quan trọng cần nhớ:
- Query parameters luôn là optional (khác với path parameters)
- Server nhận query params dưới dạng strings (cần parse)
- Encoding đúng đặc biệt quan trọng (spaces, special chars, Unicode)
- Chọn naming convention và giữ nhất quán
- Luôn validate và handle edge cases
- Đừng gửi sensitive data qua query params
- Document rõ ràng cho team và user
Hãy thực hành nhiều với các ví dụ trong bài viết này và bạn sẽ thành thạo query parameters trong API testing ^^
Leave a Reply