The Web API is used for building modern websites. It provides loosely coupled, easy-to-maintain, and cross-platform features, which have become a standard for backend frameworks nowadays.

Our project is required to provide at least 10 APIs. These are listed below based on our features and user scenarios.

  • GetStatus
  • SearchCityByName
  • GetCurrentWeatherByCity
  • GetForecastWeatherByCity
  • GetUserProfile (cookie needed)
  • AddFavoriteCity (cookie needed)
  • DelFavoriteCity (cookie needed)
  • Register
  • Login
  • Logout (cookie needed)

API design

My general approach to API design is to keep it simple and easy to use. Here are some of the rules I follow:

  • Create the contract before writing any code.
  • Each API should perform only one task; avoid designing APIs with nested queries.
  • The API name should clearly indicate the method, function, and query parameters.
  • Use GET only for data fetching and POST only for data submission; do not use PUT or DELETE methods.
  • Input parameters should support fuzzy queries.
  • A successful query should return a 200 status code with a JSON body.
  • If the query result is empty, return a 200 status code with an empty JSON body.
  • For all other exceptions, return a 4xx or 5xx status code with an error message.

Here is an example,

/GetForecastWeatherByCity
Method: Get
QueryString: id={city_id}
Body (success, 200):

{"basic": {
        "id": 1,
        "cityname": "Beijing",
        "temp": "20",
        "temp_min":"15",
        "temp_max":"23",
        "sun_raise":"05:00",
        "sun_set":"17:00",
        "pressure":"1034",
        "humidy":"71",
        "feels_like":"21",
        "detail_desc":"scattered clouds",
        "simp_desc":"Clouds"
    },
    "forecast": [
    {
        "date":"2025-05-06",
        "temp": "20",
        "temp_min":"15",
        "temp_max":"23",
        "simp_desc":"Clouds"
    },
    {
        "date":"2025-05-07",
        "temp": "20",
        "temp_min":"15",
        "temp_max":"23",
        "simp_desc":"Clouds"
    },
    {
        "date":"2025-05-08",
        "temp": "20",
        "temp_min":"15",
        "temp_max":"23",
        "simp_desc":"Clouds"
    },
    {
        "date":"2025-05-09",
        "temp": "20",
        "temp_min":"15",
        "temp_max":"23",
        "simp_desc":"Clouds"
    },
    {
        "date":"2025-05-10",
        "temp": "20",
        "temp_min":"15",
        "temp_max":"23",
        "simp_desc":"Clouds"
    },
    {
        "date":"2025-05-11",
        "temp": "20",
        "temp_min":"15",
        "temp_max":"23",
        "simp_desc":"Clouds"
    },
    {
        "date":"2025-05-06",
        "temp": "20",
        "temp_min":"15",
        "temp_max":"23",
        "simp_desc":"Clouds"
    }]
}

Body (no result, 200):

{"cities": []}

Body (others, 404)

{"error": "{exception msg}"}

Questions

During my design process, I was considering how to name the APIs, define the paths, and set the parameters to follow the “best practices”. However, after deeper investigation, I realized that my API design style is closer to the RPC style rather than the RESTful style. There are many differences between these two approaches.

Feature RPC Style RESTful Style
Design Philosophy Remote Procedure Call Resource Model and Uniform Interface
Path Design Includes operation name and parameters Uses nouns to represent resources
HTTP Methods Mainly uses GET and POST Fully utilizes GET, POST, PUT, DELETE, etc.
Parameter Passing Query parameters or request body Path parameters and query parameters
Resource Model Does not emphasize the concept of resources Emphasizes the concept of resources
Scalability and Flexibility Paths increase when adding new operations Expanded by adding new resources or resource collections
Applicable Scenarios Complex business logic Scenarios with a clear resource model

Best practices for RPC-Style APIs

  1. Use clear and consistent method naming
  • Follow action-oriented naming conventions: getUser, createOrder, updateInventory.
  • Use namespaces or service names if needed, e.g., user.getProfile.
  1. Keep method interfaces simple
  • Prefer single input and single output message formats (e.g., request and response objects).
  • Avoid excessive nesting in request/response objects.
  1. Version your services
  • Use explicit versioning in the service name or endpoint: UserServiceV2, v1.UserService.
  • Avoid breaking changes without incrementing versions.
  1. Define strong contracts
  • Use schemas (e.g., Protobuf for gRPC) to enforce strict typing.
  • Leverage tools for API linting and validation.
  1. Error handling
  • Standardize error codes and messages.
  • Use consistent and structured error formats with fields like code, message, and details.
  1. Optimize for performance
  • Use efficient binary protocols (e.g., gRPC with Protobuf) for better performance and lower latency.
  • Enable streaming for large or continuous data transfers when appropriate.
  1. Authentication & authorization
  • Apply authentication at the transport layer (e.g., TLS, mTLS).
  • Use tokens (JWT, OAuth2) and scopes for access control.
  1. Documentation and tooling
  • Generate client/server stubs from interface definitions.
  • Auto-generate and publish service documentation.

Best Practices for RESTful-Style APIs

  1. Use resource-based URIs
  • Use nouns, not verbs: /users, /orders/123, not /getUser.
  • Maintain consistent URI structure.
  1. Proper HTTP methods
  • GET – Retrieve resources
  • POST – Create a new resource
  • PUT – Update/replace an existing resource
  • PATCH – Partially update a resource
  • DELETE – Remove a resource
  1. Use standard HTTP status codes
  • 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error, etc.
  1. Pagination, filtering, and sorting
  • Support pagination: ?page=1&limit=20
  • Filtering: ?status=active
  • Sorting: ?sort=created_at&order=desc
  1. Use HATEOAS (Hypermedia as the engine of application state)
  • Include relevant links in responses to guide clients:
{
 "id": 1,
 "name": "John",
 "links": [
   { "rel": "self", "href": "/users/1" },
   { "rel": "orders", "href": "/users/1/orders" }
 ]
}
  1. Statelessness
  • Each request should contain all necessary context (e.g., tokens).
  • Avoid server-side session state.
  1. Content negotiation
  • Support Accept and Content-Type headers (e.g., application/json).
  • Provide meaningful error messages in the same format.
  1. API versioning
  • Use URI versioning (/v1/users) or header-based versioning (Accept: application/vnd.api+json;version=1).
  1. Security
  • Use HTTPS.
  • Implement authentication (OAuth2, API keys).
  • Validate and sanitize all inputs to avoid injection attacks.
  1. Documentation
  • Provide OpenAPI (Swagger) specs.
  • Ensure examples, status codes, and data models are clear.

Some findings and thoughts

  • RESTful-style APIs focus more on resource operations, while RPC-style APIs are more focused on functions and methods.
  • “Best Practices” do not mean a single standard. Different API styles can all achieve the same goals. However, following a shared standard can improve team collaboration, especially in large engineering teams.