API Best Practices
Design clean, consistent, and maintainable APIs.
RESTful Design
Resource Naming
Use plural nouns for resources:
✅ GET /api/products
✅ GET /api/users/123/orders
❌ GET /api/getProducts
❌ GET /api/user
HTTP Methods
GET- Retrieve resourcesPOST- Create new resourcesPUT- Update entire resourcePATCH- Partial updateDELETE- Delete resource
Status Codes
200 OK- Success201 Created- Resource created204 No Content- Success with no body400 Bad Request- Invalid input401 Unauthorized- Authentication required403 Forbidden- Permission denied404 Not Found- Resource not found500 Internal Server Error- Server error
Pagination
func (c *ProductController) ListProducts(ctx *gin.Context) {
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(ctx.DefaultQuery("page_size", "10"))
products, total, err := c.svc.Product().List(ctx.Request.Context(), page, pageSize)
if err != nil {
util.RespondWithError(ctx, util.NewErrorMessage("E5001", "Failed to list products", err))
return
}
util.RespondWithSuccessList(ctx, http.StatusOK, products, total, page, pageSize)
}
Response:
{
"code": "0",
"data": [...],
"total": 100,
"current": 1,
"page_size": 10
}
Filtering & Searching
func (c *ProductController) ListProducts(ctx *gin.Context) {
search := ctx.Query("search")
category := ctx.Query("category")
minPrice, _ := strconv.ParseFloat(ctx.Query("min_price"), 64)
maxPrice, _ := strconv.ParseFloat(ctx.Query("max_price"), 64)
filters := ProductFilters{
Search: search,
Category: category,
MinPrice: minPrice,
MaxPrice: maxPrice,
}
products, total, err := c.svc.Product().List(ctx.Request.Context(), filters, page, pageSize)
// ...
}
Query: GET /api/products?search=laptop&category=electronics&min_price=500&max_price=2000
Sorting
// Query: GET /api/products?sort=price:asc,name:desc
func (c *ProductController) ListProducts(ctx *gin.Context) {
sortParam := ctx.Query("sort")
// Parse sort parameter
sortFields := parseSortParam(sortParam) // [{Field: "price", Order: "asc"}, ...]
products, total, err := c.svc.Product().ListWithSort(ctx.Request.Context(), sortFields, page, pageSize)
// ...
}
Versioning
URL Versioning (Recommended)
v1 := router.Group("/api/v1")
{
v1.GET("/products", controller.V1ListProducts)
}
v2 := router.Group("/api/v2")
{
v2.GET("/products", controller.V2ListProducts)
}
Error Messages
{
"code": "E4001",
"err": "Validation failed: price must be positive"
}
- Use consistent error codes
- Provide helpful messages
- Don't expose internal details
- Use English for error messages
Rate Limiting
router.Use(middleware.RateLimit(100, 200)) // 100 req/sec, burst 200
CORS
router.Use(middleware.CORS())
API Documentation
Use Swagger/OpenAPI:
// @Summary List products
// @Description Get a list of products with pagination
// @Tags products
// @Accept json
// @Produce json
// @Param page query int false "Page number" default(1)
// @Param page_size query int false "Page size" default(10)
// @Success 200 {object} ProductListResponse
// @Failure 500 {object} ErrorResponse
// @Router /products [get]
func (c *ProductController) ListProducts(ctx *gin.Context) {
// ...
}
Best Practices Summary
DO ✅
- Use RESTful conventions
- Implement pagination for lists
- Support filtering and sorting
- Use proper HTTP methods and status codes
- Version your APIs
- Document endpoints
- Implement rate limiting
- Handle CORS properly
- Return consistent responses
- Validate all input
DON'T ❌
- Don't use verbs in URLs
- Don't ignore HTTP standards
- Don't return inconsistent responses
- Don't expose internal errors
- Don't skip validation
- Don't ignore security
- Don't forget error handling
- Don't hardcode values
- Don't skip documentation
- Don't break backward compatibility
Next Steps
- Review authentication
- Implement error handling
- Add audit logging
- Write Go tests for handlers and integration scenarios; for the React admin UI, see Frontend testing