Skip to Content
Lesson 3

Designing Flexible APIs

Workplace Context

Your proof-of-concept was a success, and the team is now able to communicate between the frontend and backend in a local development environment. However, looking ahead, your tech lead wants to ensure the API you build is not just functional but also scalable and maintainable. They’ve assigned you the task of establishing a set of best practices for API design. Your goal is to define a clear, consistent, and predictable structure for all future endpoints so that any client — whether it’s the company’s React app, a mobile app, or a partner’s integration — can easily understand and consume your API.


Lesson Objectives

  • Apply RESTful principles to design clear, predictable, and scalable APIs.
  • Implement an API versioning strategy to allow for future changes without breaking existing clients.
  • Design consistent shapes for API requests and responses, including a standardized error object.
  • Briefly describe GraphQL as a popular alternative to REST and understand its core concepts.

The Principles of REST

REST (REpresentational State Transfer) is an architectural style for designing networked applications. It’s not a strict protocol but a set of constraints that, when applied, lead to a system that is performant, scalable, and easy to work with. A well-designed RESTful API feels intuitive to use.

The core principles are:

  1. Client-Server Architecture: The client (e.g., React app) and the server (e.g., Express API) are separate. This separation of concerns allows them to evolve independently.
  2. Statelessness: Each request from a client to the server must contain all the information needed to understand and complete the request. The server does not store any client session state between requests. This is why we send tokens or session cookies with every authenticated request.
  3. Cacheability: Responses from the server should be defined as cacheable or not. This allows clients or intermediate proxies to cache responses, improving performance and scalability.
  4. Uniform Interface: This is the most critical principle for API design. It simplifies and decouples the architecture, which allows each part of the system to evolve independently. It has four sub-constraints:
    • Resource-Based: APIs are designed around resources (e.g., users, products, posts). You interact with these resources using standard HTTP methods.
    • Manipulation of Resources Through Representations: The client holds a representation of a resource (e.g., a JSON object). When it wants to update that resource, it sends this representation to the server.
    • Self-Descriptive Messages: Each request should be self-contained and describe how to process it. For example, using the Content-Type header to specify that the body is JSON (application/json).
    • Hypermedia as the Engine of Application State (HATEOAS): The API response should include links to other related actions or resources. For example, a response for a user might include a link to get all of that user’s posts.

Resource Naming Conventions

  • Use nouns, not verbs (e.g., /products, not /getProducts).
  • Use plural nouns for collections (e.g., /users).
  • Use kebab-case for long resource names (e.g., /product-reviews).
  • Use the resource ID in the path for specific items (e.g., /users/123).

Standard HTTP Methods

MethodActionExample Request
GETRetrieve a resource or a collectionGET /api/users
POSTCreate a new resourcePOST /api/users
PUTReplace/update an existing resourcePUT /api/users/123
PATCHPartially update an existing resourcePATCH /api/users/123
DELETEDelete an existing resourceDELETE /api/users/123

API Versioning

As your application grows, you will inevitably need to make breaking changes to your API. You might need to rename a field, change a data type, or remove an endpoint entirely. To do this without breaking existing client applications, you should version your API.

The most common and straightforward approach is URI path versioning. You simply include the version number in the URL path.

  • https://api.example.com/v1/products
  • https://api.example.com/v2/products

This is clear, explicit, and easy to implement in your Express router:

// server.js const v1ProductRoutes = require('./routes/v1/products'); const v2ProductRoutes = require('./routes/v2/products'); app.use('/api/v1/products', v1ProductRoutes); app.use('/api/v2/products', v2ProductRoutes);

When you introduce v2, you can maintain v1 for a period of time, giving client developers a chance to migrate.


Consistent Request and Response Shapes

A predictable API is an easy-to-use API. You should establish a consistent structure for the data you send and receive.

Standardized Error Responses

Don’t just send a 404 status with the text “Not Found”. Provide a consistent JSON error object that clients can reliably parse.

// A good error response { "status": "error", "error": { "message": "The requested product could not be found.", "code": "RESOURCE_NOT_FOUND" } }

You can create a custom error handling middleware in Express to ensure all errors are formatted this way.

Consistent Success Responses

Similarly, for successful requests, you might want to wrap your data in a consistent structure.

// GET /api/v1/users { "status": "success", "data": [ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" } ] }

While not as critical as error shapes, this can help clients differentiate between the metadata of the response (status) and the actual data payload.


Introduction to GraphQL

GraphQL is a query language for your API and a server-side runtime for executing those queries. It was developed by Facebook and is now a popular alternative to REST.

Unlike REST, which has many endpoints for different resources, a GraphQL API typically has a single endpoint. The client sends a “query” to this endpoint that specifies exactly what data it needs.

Key Differences from REST:

FeatureRESTGraphQL
EndpointsMultiple endpoints (/users, /posts)Typically a single endpoint (/graphql)
Data FetchingServer defines the data shape; often leads to over/under-fetching.Client specifies the exact data it needs; no over-fetching.
Request StructureUses HTTP methods (GET, POST, etc.) and URL paths.Uses a custom query language (sent via a POST request).
Strongly TypedNo built-in type system (requires external tools like OpenAPI).Has a built-in, strongly-typed schema that defines the API.

Example GraphQL Query:

A client can ask for a user and their posts in a single request, and only get the fields they care about:

query { user(id: "1") { id name posts { title } } }

This query would return a JSON object with the exact same shape. This solves the problem of over-fetching (getting fields you don’t need) and under-fetching (having to make multiple requests to get all the data you need).


Summary

In this lesson, you learned the principles of designing robust and developer-friendly APIs. We covered the core constraints of REST, including resource-based URLs and the proper use of HTTP methods. You learned the importance of versioning your API to support future changes and the value of designing consistent JSON shapes for both successful responses and errors. Finally, you were introduced to GraphQL as a powerful alternative to REST that gives clients the power to request exactly the data they need, solving common issues like over- and under-fetching.


References

Additional Resources