Chapter 14
API
To be more efficient and design more robust solutions, developers follow certain principles, including:
-
Separation of concerns: code is divided into coherent modules. Most of the time, a module is a set of features related to a business domain. It provides a service independently, hiding its complexity from the outside.
Example: I have an e-commerce website composed of an order manager, a product catalog, a payment module... The order manager uses the payment module to charge the customer. It doesn't need to know the details of how the payment is processed, just that it works. -
Don't reinvent the wheel: instead of wasting time coding everything from scratch, we rely on someone else's work.
This can come from another company, an internal service, a community, etc. The service provided can take many forms: free vs. paid, open-source vs. proprietary, downloadable libraries vs. online services...
Example: a library I can integrate into my code to manage dates across different time zones.
Generally, due to time invested and expertise, the service delivered by a third party is of much better quality than what we could produce ourselves: business rules we were unaware of, ensured security...
There are exceptions, cases where we prefer to build our own systems: the third-party service is too expensive, we want a custom solution, or we don’t want to put the company's core business at risk...
Software and applications are therefore composed of dozens of sub-elements (business modules, third-party libraries...) that must communicate and work together without knowing the details of each other's inner workings.
Application Programming Interface
Let’s dive straight into an analogy. You’re in a car, and to drive it, the manufacturer provides various interfaces: pedals, gear stick, steering wheel, electronic controls... Each interface controls an underlying system (engine, backup camera...) without you needing to understand its detailed functioning. In fact, the manufacturer intentionally hides components like drive belts and fluid reservoirs from the cabin.
In software development, it’s the same. A service developer provides an interface to use their product. To guide the user, only certain entry points representing key features are made public, while internal details remain private.
These public entry points are what we call the API: Application Programming Interface.
As web developers, we mainly build and use two types of APIs:
1 - APIs for libraries or software modules
During development, developers emphasize in their code the concept of "contract." Using the capabilities of the programming language, they guide how their module should be used.
Example:
I develop a library for sending emails, intended for integration into other projects.
Internally, the main component of my library, "EmailSender," does many things: validating email addresses, encoding attachments... As the library's developer, I want users to understand they don’t need to trigger these functionalities themselves; everything is handled automatically.
In fact, I will block them from doing so by exposing only one public function : "sendEmail".
To establish a contract, I create a file, "AbleToSendEmail," explaining how to send an email: the function to call, its inputs, and outputs. I then associate "EmailSender" with "AbleToSendEmail" so that it adheres to its structure. From then on, we know "EmailSender" can send an email if a specific function is called with the right input.
All contracts respected by "EmailSender" form its API.
Why separate the API into contracts instead of directly reading the code of "EmailSender"?
In coding, we aim to depend on contracts, not directly on components. This means whoever calls the "sendEmail" function knows they’re using something "able to send an email" but are not aware that it’s specifically "EmailSender." They could use any other component adhering to the same contract.
This is useful when creating a "fake email sender" for testing purposes, so emails aren't sent during automated tests. We could create a "MockEmailSender" component, also tied to "AbleToSendEmail."
Explicitly declaring the API enables portability. Clients can switch service providers seamlessly without changing a single line of code.
Several communities, like the PHP Framework Interop Group, propose well-designed API standards for common tasks: sending HTTP requests, caching, logging events, retrieving the current time... Although these are proposals, they’ve become de facto standards. Clients now only use components adhering to these APIs.
2 - Remote APIs
These connect systems over a network.
Example: your Bluetooth headset and smartphone both expose APIs to detect each other, connect, and allow you to control playback with a button on the headset.
The most common remote APIs are web APIs, leveraging the HTTP protocol. HTTP natively supports remote resource control. REST, one of the most widely used API architectures, is built around HTTP, particularly its verbs: GET (retrieve information), POST (create), DELETE (delete), PUT/PATCH (update).
Example: "GET /offer/42" retrieves the details of offer #42.
Over the last decade, web APIs have grown exponentially.
Startups have massively adopted API-first architectures, enabling rapid data exchange and integrations. Services like Dropbox, Twilio, and Shopify have proven APIs can be a business model in themselves. Since then, we hear daily about new services launching as Software as a Service (SaaS) : a software relying primarily on web APIs.
Some tools specialize in interconnecting these SaaS services, enabling complete production chains without any technical expertise, popularizing the "no-code" movement.
Today, "API" and "web API" are often conflated. In this chapter, I’ll also just use "API" interchangeably for simplicity.
Design
The "I" in API stands for Interface. An interface implies a user. A user implies the API must be user-friendly, logical, scalable... It must be thoughtfully designed with a product vision.
In the object-oriented programming chapter, we discussed Domain-Driven Design (DDD): an approach to designing code that mirrors real-world business domains. Before starting a project, it’s crucial to fully brief developers on the business domain: its vocabulary, concepts, structure, current needs, and future ideas.
Modern API design follows the same logic.
APIs and the backend code they trigger are typically designed simultaneously, based on the same business concepts. We strive to develop a coherent system using consistent vocabulary and data organization between both.
But there are still differences!
Let's take this example: a user logs into your website from their account portal. To build the page, the frontend sends an HTTP request to the backend API to fetch their username, communication preferences, subscription history, payment methods... The frontend opts to request all this data at once, saving precious seconds and caching everything in one go.
The API response might look like this:
{
"user": {
"username": "toto",
"communications": {
"newsletter": true,
"promotions": false
},
"subscriptions": [
{
"plan": "premium",
"startDate": "2024-12-14",
"endDate": "2025-12-14",
"status": "active"
},
{
"plan": "family",
"startDate": "2023-07-01",
"endDate": "2024-02-14",
"status": "expired"
}
]
}
}
Somewhere in the backend, we can find as well the business entities: user, subscription, plan, communication...
However:
- The backend "user" entity includes additional fields like password, registration date... These aren’t exposed in the API response.
- User management and subscription management are handled by separate modules. Fetching a user's subscriptions is costly. Since 95% of nominal use cases don’t require subscriptions, the backend "user" entity doesn’t include a "subscriptions" attribute. It’s only during this frontend request that the user manager queries the subscription manager to populate the "subscriptions" attribute for the API "user" entity.
- Dates are expressed in local time in the API but in UTC in the backend.
- The backend uses technical subscription statuses like "100" or "200," while the API exposes human-readable ones: "active," "expired."
These differences require a translation layer between the API and backend. This allows customization of API responses and ensures either can evolve independently.
Exposing the backend's internal workings directly through the API is a bad practice. It makes changes impossible without assessing the impact on all API clients. If you encounter such a backend, it’s likely there’s no translation layer between it and its data sources either. The API directly exposes the structure and vocabulary of those sources, including external partners. Clients become tightly coupled to the partner, making migrations or updates a long and painful process.
This extreme, which I’ve personally encountered more than once, underscores the importance of thoughtful API design from the start. A poorly designed API becomes very hard to fix once it’s opened to clients.
Key questions to consider:
- What features should it offer?
- What is the update strategy? Should versioning be implemented?
- What sequence of calls will the client need to make to retrieve data?
- Is the vocabulary and concept usage consistent throughout?
- And the most important: is it intuitive and easy to integrate?
A well-designed API is a clear competitive advantage.
The best example: Stripe, the payment processor.
Despite being more expensive and not the best in its field, Stripe has outshone competitors thanks to its exceptional API quality.
APIs are a vast topic, which we will explore in a series of chapters. We’ll cover themes such as documentation, discoverability, security, interoperability, and monetization.