Skip to main content

API Best Practices

Developer Intermediate

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 resources
  • POST - Create new resources
  • PUT - Update entire resource
  • PATCH - Partial update
  • DELETE - Delete resource

Status Codes

  • 200 OK - Success
  • 201 Created - Resource created
  • 204 No Content - Success with no body
  • 400 Bad Request - Invalid input
  • 401 Unauthorized - Authentication required
  • 403 Forbidden - Permission denied
  • 404 Not Found - Resource not found
  • 500 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

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 ✅

  1. Use RESTful conventions
  2. Implement pagination for lists
  3. Support filtering and sorting
  4. Use proper HTTP methods and status codes
  5. Version your APIs
  6. Document endpoints
  7. Implement rate limiting
  8. Handle CORS properly
  9. Return consistent responses
  10. Validate all input

DON'T ❌

  1. Don't use verbs in URLs
  2. Don't ignore HTTP standards
  3. Don't return inconsistent responses
  4. Don't expose internal errors
  5. Don't skip validation
  6. Don't ignore security
  7. Don't forget error handling
  8. Don't hardcode values
  9. Don't skip documentation
  10. Don't break backward compatibility

Next Steps