REST is the de facto standard for web APIs, but it is neither the only tool nor always the best one. In recent years alternatives have matured that attack very specific limitations: GraphQL was born from the problem of requesting tailored data from mobile; gRPC, from the need for high-performance internal communication; webhooks, from HTTP's inability to let the server notify the client. This lesson closes the module by drawing the complete map of integration styles, with examples applied to Aroma Store, so that you can choose with judgement and — most importantly — understand that these approaches do not compete: they coexist within the same company, each in its own place.
Contents
- Why a single style is not enough
- GraphQL: the client decides which fields it wants
- What problems GraphQL solves and which ones it introduces
- gRPC: a strong contract and high performance between services
- Event-driven communication: webhooks
- Queues, streaming and real time: SSE and WebSockets
- Comparison table of the four approaches
- Honest decision criteria
- Aroma Store's final architecture
- Why a single style is not enough
Aroma Store's communication needs are not homogeneous. Compare these four cases:
| Need | Characteristics | Does it fit REST well? |
|---|---|---|
| A blog displays the catalogue | Public, read-only, cacheable, unknown client | Perfectly |
| Aroma Mobile's order screen | Needs order, customer, coffee and shipping data at once | So-so: several requests |
| The orders service queries stock 500 times a second | Internal, high volume, latency-critical | Mediocre: JSON and HTTP/1 overhead |
| Notifying SwiftShip that an order has been paid | The sender is the server; the receiver is external | Badly: HTTP only goes client → server |
Each mismatch has a specific answer. Seeing them together gives you judgement; using them all at once for no reason gives you an ungovernable architecture.
- GraphQL: the client decides which fields it wants
GraphQL is a query language for APIs, created at Facebook in 2012 and published in 2015. Its three fundamental decisions:
- A typed schema defines all the available data and its relationships. It is the contract, and it is mandatory.
- A single URL (typically
/graphql), which youPOSTto. - The client writes the query: it asks for exactly the fields it needs, not one more.
The Aroma Store schema
type Coffee {
id: ID!
name: String!
origin: String!
roast: Roast!
priceEuros: Float!
stock: Int!
tastingNotes: [String!]!
reviews: [Review!]!
}
type Review {
id: ID!
author: String!
rating: Int!
comment: String
}
enum Roast { LIGHT MEDIUM DARK }
type Query {
coffee(id: ID!): Coffee
coffees(origin: String, roast: Roast, limit: Int): [Coffee!]!
}The ! sign means "cannot be null". The schema is documentation, validation and contract all at once: tools read it and offer autocompletion as you write queries.
The query
For its catalogue screen, the mobile app needs only the name, the price and the average rating. It asks for it like this:
And it receives exactly that, with the same shape as the query:
{
"data": {
"coffees": [
{
"id": "cof_001",
"name": "Ethiopia Yirgacheffe",
"priceEuros": 14.50,
"reviews": [{ "rating": 5 }, { "rating": 4 }]
}
]
}
}Over HTTP, the actual request is a POST:
curl -X POST https://api.aromastore.example/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"query":"{ coffees(origin: \"Ethiopia\", limit: 10) { id name priceEuros reviews { rating } } }"}'The REST equivalent
Achieving the same thing with the Aroma Store REST API would take several calls:
# 1. The Ethiopian coffees (returns ALL the fields of each coffee)
curl "https://api.aromastore.example/v1/coffees?origin=Ethiopia&limit=10"
# 2. Each coffee's reviews: one request per coffee
curl "https://api.aromastore.example/v1/coffees/cof_001/reviews"
curl "https://api.aromastore.example/v1/coffees/cof_007/reviews"
curl "https://api.aromastore.example/v1/coffees/cof_012/reviews"
# ... and so on for all tenEleven requests against one, and the first one downloads origin, roast, stock and tastingNotes that the screen does not use. That is exactly GraphQL's argument.
It is worth qualifying that, in fairness: REST has partial answers to this problem — expansion parameters (?include=reviews), field selection (?fields=name,priceEuros) and aggregate endpoints designed for one specific screen. The thing is that in REST these are ad hoc conventions that each API solves in its own way, whereas in GraphQL it is the model itself.
- What problems GraphQL solves and which ones it introduces
Problems it solves
| Problem | Explanation |
|---|---|
| Over-fetching | Receiving more data than you need. The screen wants the name and the price, and receives fifteen fields. |
| Under-fetching (N+1 requests) | One request not being enough, so N more are needed to complete the information. |
| Proliferation of bespoke endpoints | Without GraphQL, every new screen tends to spawn its own specific endpoint. |
| Contract evolution | Adding fields breaks nobody, because each client asks for its own; unused fields are marked as deprecated and you can measure who still uses them. |
| Out-of-date documentation | The schema is executable and introspectable: it cannot lie. |
Problems it introduces
| Problem | Explanation |
|---|---|
| HTTP caching | Everything goes by POST to one URL. You lose browser, proxy and CDN caching, and have to replace it with client-level and field-level caching, which is far more complex. |
| Query complexity | A client can ask for deeply nested relationships and bring the server down. You have to limit the depth, complexity and cost of each query. |
| N+1 in the database | The flexibility is paid for in the resolver: asking for the reviews of 10 coffees can fire 11 SQL queries if batching techniques are not used. |
| Security and authorisation | Permissions must be applied field by field, not per endpoint. |
| Status codes | Errors arrive in an errors array with 200 OK: HTTP-based monitoring sees nothing. |
| Usage limits | "100 requests per minute" means nothing if one query can cost a thousand times more than another. |
| File and binary uploads | Not part of the model; they require extensions. |
| Learning curve | Schema, resolvers, fragments, normalised caching and its own tooling. |
Note something you can already recognise: in Richardson's terms, GraphQL is a level 0 — one endpoint, everything by POST, the operation inside the body. The difference from the swamp of POX is that here the choice is deliberate and comes with a typed contract, introspection and a tooling ecosystem that compensates for what is lost.
- gRPC: a strong contract and high performance between services
gRPC (Google, 2015) is modern RPC. Its pillars:
- Protocol Buffers (protobuf): a compact, typed binary format for serialising messages.
- HTTP/2 as transport, with multiplexing and compressed headers.
- A contract in a
.protofile from which client and server code is generated in more than ten languages. - Four communication modes, including streaming in both directions.
The contract of the Aroma Store inventory service
syntax = "proto3";
package aromastore.inventory.v1;
// Internal inventory service: consumed by the orders, catalogue
// and warehouse services. It is not reachable from the internet.
service Inventory {
// One-off query of a coffee's stock
rpc GetStock (GetStockRequest) returns (StockResponse);
// Reserves units when an order is confirmed
rpc ReserveStock (ReserveStockRequest) returns (StockResponse);
// Continuous flow of stock changes (server streaming):
// the internal panel subscribes and receives live updates
rpc WatchChanges (WatchChangesRequest) returns (stream StockChange);
}
message GetStockRequest {
string coffee_id = 1; // the number is the position in the binary format
}
message ReserveStockRequest {
string coffee_id = 1;
int32 units = 2;
string order_id = 3;
}
message StockResponse {
string coffee_id = 1;
int32 available = 2;
int32 reserved = 3;
}
message StockChange {
string coffee_id = 1;
int32 available = 2;
string timestamp = 3; // ISO 8601 timestamp
}The numbers (= 1, = 2) are not values: they are the field tags that take the place of names in the binary format. That is why protobuf is so compact — it does not send the field names — and why those numbers must never be changed once published: they are the real contract.
From Node.js, consuming it looks like a function call:
// The orders service checks the stock before confirming
const response = await inventoryClient.getStock({ coffee_id: 'cof_001' });
if (response.available < requestedUnits) {
throw new Error('Insufficient stock for coffee ' + response.coffee_id);
}The four gRPC modes
| Mode | Description | Example in Aroma Store |
|---|---|---|
| Unary | One request, one response | GetStock |
| Server streaming | One request, many responses | WatchChanges: live stock changes |
| Client streaming | Many requests, one response | Bulk inventory upload after a stocktake |
| Bidirectional | A continuous flow in both directions | Real-time synchronisation with the warehouse |
Strengths and limits
In favour: messages far smaller than JSON, faster serialisation, a strong contract with code generation, native streaming, excellent for high-volume internal traffic and for communication between services written in different languages.
Against: it is not directly consumable from a browser (it needs a gateway such as gRPC-Web), binary messages cannot be read with your eyes or debugged with curl, it does not make use of HTTP caching, and the tooling is poorer outside prepared environments. It is not a good candidate for a public API.
- Event-driven communication: webhooks
Everything we have seen so far shares one limitation: the client asks and the server answers. So how does Aroma Store notify SwiftShip that an order has been paid and is ready for collection?
The naive option is polling: SwiftShip checks every minute.
# SwiftShip asking over and over... almost always for nothing
curl "https://api.aromastore.example/v1/orders?status=paid&dateFrom=2026-08-14T09:00:00Z"With one order every half hour and one query per minute, 98% of the requests are useless: they burn resources on both sides and the notification still arrives up to a minute late.
A webhook reverses the direction: the consumer registers a URL of its own, and the provider POSTs to it when something happens. It is, literally, "an API in reverse": now Aroma Store is the HTTP client and SwiftShip the server.
sequenceDiagram
participant AS as Aroma Store
participant SS as SwiftShip
Note over SS,AS: Prior registration (once only)
SS->>AS: POST /v1/webhooks<br/>{"url":"https://api.swiftship.example/aroma",<br/> "events":["order.paid"]}
AS-->>SS: 201 Created
Note over AS: The event happens
AS->>SS: POST https://api.swiftship.example/aroma<br/>{"type":"order.paid", ...}
SS-->>AS: 200 OK (acknowledgement)
Sending the event:
POST /aroma HTTP/1.1
Host: api.swiftship.example
Content-Type: application/json
Aroma-Event-Id: evt_9f2c
Aroma-Signature: sha256=7d38cb...
{
"id": "evt_9f2c",
"type": "order.paid",
"date": "2026-08-14T09:20:11Z",
"data": {
"orderId": "ord_5001",
"customerId": "cus_842",
"totalEuros": 29.00,
"shippingAddress": {
"street": "Carrer de Mallorca 120",
"city": "Barcelona",
"postcode": "08036"
}
}
}Four design decisions you will see in every serious webhook and that are worth internalising right away:
- Cryptographic signature (
Aroma-Signature): the receiver recalculates an HMAC of the body with a shared secret and checks that it matches. Without this, anyone who knows the URL can invent events. - Event identifier (
Aroma-Event-Id): it lets the receiver detect duplicates. Webhooks guarantee "at least once" delivery, so the receiver must be idempotent. - Retries with increasing backoff: if the receiver does not respond with a
2xx, the sender retries at ever longer intervals over hours, and raises an alert if it eventually gives up. - Namespaced event type (
order.paid,order.shipped,review.published): it allows selective subscription and the addition of new events without breaking anything.
| Polling | Webhook | |
|---|---|---|
| Who initiates | The consumer | The provider |
| Notification latency | Up to the polling interval | Almost immediate |
| Useless requests | Many | None |
| Requirement on the consumer | None | Needs a publicly reachable URL |
| Complexity | Very low | Retries, signatures, duplicates |
| Reliability | High (if it fails, it just retries) | Requires careful design |
Webhooks do not replace the REST API: they complement it. The usual approach is for the event to carry just enough and for the receiver to call the API afterwards to obtain the complete, up-to-date detail.
- Queues, streaming and real time: SSE and WebSockets
Let's complete the map with three mechanisms that will turn up in your professional life:
- Message queues and streaming (RabbitMQ, Kafka, SQS): the internal equivalent of webhooks. The sender publishes an event to a broker and interested parties consume it at their own pace. They provide persistence, retries, ordering and total decoupling. Aroma Store would use them so that the invoicing, loyalty and analytics services can react to
order.paidwithout the orders service even knowing they exist. - Server-Sent Events (SSE): a long-lived HTTP channel over which the server sends messages to the client. Unidirectional, simple, over ordinary HTTP, with automatic reconnection built into the browser. Ideal for the Aroma Store internal panel showing orders as they come in.
- WebSockets: a persistent bidirectional channel over a connection upgraded from HTTP. Necessary when both ends talk continuously: a customer service chat, for example.
| Mechanism | Direction | Over HTTP | Typical case |
|---|---|---|---|
| Webhook | Server → another server | Yes (POST) |
Integration between companies |
| Queue / streaming | Producer → consumers | No | Events between internal services |
| SSE | Server → browser | Yes | Live panel, notifications |
| WebSocket | Bidirectional | Only the handshake | Chat, real-time collaboration |
A useful rule: if the user has to find out about something without asking for it, you need one of these four; no REST, GraphQL or unary gRPC API solves it on its own.
- Comparison table of the four approaches
| Criterion | REST | GraphQL | gRPC | Webhooks / events |
|---|---|---|---|---|
| Model | Resources and HTTP verbs | Queries over a schema | Procedure calls | Notification of occurrences |
| Transport | HTTP | HTTP (POST) |
HTTP/2 | HTTP (POST) or broker |
| Format | JSON (or others) | JSON | Binary (protobuf) | JSON |
| Contract | Optional (OpenAPI) | Mandatory (schema) | Mandatory (.proto) |
Documented per event |
| Typing | Weak unless there is a schema | Strong | Strong | Weak |
| Coupling | Low | Medium | High (shared contract) | Very low |
| HTTP caching | Native and free | Difficult | Not applicable | Not applicable |
| Streaming | No (use SSE/WS) | With subscriptions | Native, in 4 modes | Asynchronous by nature |
| From a browser | Direct | Direct | Needs a gateway | Not applicable |
| Debugging | curl, browser |
Its own tooling | Needs tooling | Delivery log |
| Target audience | Anyone, third parties included | Your own clients with rich screens | Internal services | Partners and integrations |
| Learning curve | Gentle | Medium-high | Medium-high | Medium (reliability) |
| Strong point | Simplicity, caching, universality | Tailored data in one call | Performance and a strong contract | Notifications without polling |
| Weak point | Over/under-fetching | Caching and cost control | Not suitable for the public | Delivery and duplicates |
- Honest decision criteria
Concrete questions, in order of importance:
- Who consumes the API? If it is third parties you do not control, REST. The lowest barrier to entry wins almost every time; a public API in gRPC would be a mistake.
- Is the data cacheable? If the catalogue is queried thousands of times and changes little, REST's free HTTP caching is a weighty argument that is hard to match.
- Do your clients need highly variable combinations of data? If you have many different screens over the same model and genuinely suffer from over/under-fetching, GraphQL delivers real value.
- Is it internal communication with high volume and critical latency? gRPC. You control both ends, so the contract coupling does not hurt and the performance is noticeable.
- Is the server the sender? Webhooks outwards, queues inwards. There is no debate: client-server HTTP does not cover this case.
- What is the size and experience of your team? An excellent architecture nobody knows how to operate is worse than a good one everybody understands. Badly operated GraphQL is an inexhaustible source of performance incidents.
Three warnings, from the industry's accumulated experience:
- Do not adopt GraphQL just to avoid two requests. With HTTP/2 several small requests are cheap, and REST supports expansion parameters and field selection.
- Do not use gRPC facing outwards unless your consumers are technical teams capable of integrating it.
- Do not implement webhooks without signatures, retries and idempotency. A badly built webhook produces duplicated orders or lost events, and both show up in the accounts.
- Aroma Store's final architecture
With the whole map on the table, here is the team's decision, and it is the one we will follow for the rest of the course:
graph TD
subgraph Outside
W["Web shop (SPA)"]
M["Aroma Mobile"]
B["Blogs and comparison sites"]
SS["SwiftShip"]
end
subgraph "Public and internal API"
API["REST API v1<br/>Node.js 20 + Express<br/><i>api.aromastore.example/v1</i>"]
end
subgraph "Internal services"
INV["Inventory service"]
BIL["Invoicing service"]
end
W -->|REST/JSON| API
M -->|REST/JSON| API
B -->|public REST/JSON| API
API -->|gRPC| INV
API -->|gRPC| BIL
API -->|"webhook: order.paid"| SS
| Need | Technology chosen | Reason |
|---|---|---|
| Public catalogue and reviews API | REST + JSON | Frictionless adoption, cacheable, testable with curl |
| Web, mobile app and internal panel | REST + JSON | A single stable contract for three in-house clients |
| Stock queries between services | gRPC | High volume, low latency, strong contract, both ends in-house |
| Notifications to SwiftShip | Signed webhooks | The sender is the server; it avoids constant polling |
| Live orders in the internal panel | SSE | Unidirectional and simple, over standard HTTP |
| Events between internal services | Message queue | Decouples invoicing, loyalty and analytics |
And an equally important decision: GraphQL, for now, no. The team has assessed it and concluded that its screens are few and stable, that the catalogue cache is an asset it does not want to lose and that the team is small. It is a revisable decision, taken with arguments and written down. That is what designing looks like; the opposite is following fashion.
Common Mistakes and Tips
- Choosing by fashion rather than by problem. Always ask what specific limitation you are suffering from today. If you cannot name it, do not change technology.
- Believing that GraphQL replaces REST. They are complementary. Many companies expose GraphQL for their own clients and REST for third parties, over the same backend.
- Forgetting that GraphQL loses HTTP caching. If your traffic is mostly reads of slowly changing data, that loss can cost more than you save in requests.
- Using gRPC in the browser without a gateway. It does not work directly: you need gRPC-Web and a proxy that translates.
- Changing the field numbers in a
.proto. It breaks binary compatibility silently and in a way that is hard to diagnose. The numbers are the contract. - Treating a webhook as a guaranteed, single delivery. It arrives at least once, and sometimes more. Without idempotency in the receiver, you will get duplicates.
- Putting complete, sensitive data in the webhook body. Send the minimum and let the receiver query the API if it needs more: the event may arrive late and with data that is already out of date.
- Tip: keep a simple rule — REST outwards, gRPC inwards, events for anything asynchronous — and deviate from it only with a reason you can write down in two lines.
Exercises
Exercise 1: choose the right technology
For each Aroma Store need, choose between REST, GraphQL, gRPC, a webhook or SSE, and justify it with two arguments:
- An external price comparison site wants to query the catalogue every hour.
- The orders service checks stock 800 times a second before confirming purchases.
- Aroma Mobile's "my account" screen shows the customer's details, their last three orders, the shipping status of each one and their reviews.
- The roasting supplier must find out as soon as a coffee's stock drops below 20 units.
- The warehouse panel shows orders as they come in, without reloading.
Exercise 2: design a complete webhook
Design the order.shipped webhook that Aroma Store will send to any customer who requests it. Specify: the event's JSON body, the necessary headers, what the receiver must respond, what the sender will do if it does not respond and which security measures you include.
Exercise 3: compare request cost
The coffee detail screen in Aroma Mobile needs: the name, price, stock, the last five reviews (author and rating) and the roaster's name.
- How many REST requests would a naive design take?
- Write the equivalent GraphQL query.
- Propose two solutions within REST that reduce the number of requests without adopting GraphQL.
- What is lost in each case?
Solutions
Solution 1
- REST. It is an unknown third party that must be able to integrate without friction, and the catalogue is public, cacheable content:
Cache-Controlmeans many of those queries never even reach the server. - gRPC. It is internal traffic with both ends under your control, where the binary format and HTTP/2 reduce latency and CPU compared with JSON; in addition the
.protocontract prevents type errors on a critical path. - GraphQL would be the ideal candidate, as it combines four data sources into a single query, avoiding the N+1 request problem. That said, if it is the only screen with that problem, the pragmatic answer is an aggregate REST endpoint (
GET /v1/customers/cus_842/summary): it solves the case without introducing a whole technology. - Webhook. The sender is Aroma Store and the receiver is an external company; constant polling would be wasteful and would add delay to the notification.
- SSE. It is a unidirectional flow from the server to the browser, it works over ordinary HTTP and it reconnects on its own. A WebSocket would be needlessly complex because the panel sends nothing back.
Solution 2
Event body:
{
"id": "evt_a41d",
"type": "order.shipped",
"date": "2026-08-15T11:04:00Z",
"version": "1",
"data": {
"orderId": "ord_5001",
"customerId": "cus_842",
"carrier": "SwiftShip",
"trackingNumber": "SS9928374ES",
"estimatedDelivery": "2026-08-17"
}
}Headers:
POST /webhooks/aroma HTTP/1.1
Content-Type: application/json
Aroma-Event-Id: evt_a41d
Aroma-Event-Type: order.shipped
Aroma-Signature: sha256=7d38cb...
Aroma-Date: 2026-08-15T11:04:00Z
User-Agent: AromaStore-Webhooks/1.0What the receiver must respond: a 2xx (ideally 200 OK or 204 No Content) as soon as possible, before processing the event. The rule is accept, enqueue and process asynchronously: if you take a long time to respond because you are doing heavy work, the sender may treat it as a failure and retry, generating duplicates.
If it does not respond: retries with increasing backoff (for example, after 30 s, 2 min, 10 min, 1 h, 6 h and 24 h), a log of every attempt available to the consumer, an email alert after several failures and deactivation of the endpoint after a number of consecutive failures.
Security:
- HMAC-SHA256 signature of the body with a shared secret, which the receiver verifies before processing anything.
- A timestamp in the signature to reject old resends (replay attacks).
- HTTPS mandatory on the destination URL.
- An event identifier so the receiver can discard duplicates.
- Minimal data: no payment data and no unnecessary personal data; if more is needed, let the receiver query the API with authentication.
Solution 3
1. REST requests with a naive design: three.
curl https://api.aromastore.example/v1/coffees/cof_001
curl "https://api.aromastore.example/v1/coffees/cof_001/reviews?limit=5"
curl https://api.aromastore.example/v1/roasters/ros_032. GraphQL query: one.
query {
coffee(id: "cof_001") {
name
priceEuros
stock
roaster { name }
reviews(limit: 5) { author rating }
}
}3. Two solutions within REST:
- An expansion parameter:
GET /v1/coffees/cof_001?include=reviews,roaster, which returns the related resources embedded in the same response. A single request, and it is still a cacheableGET. - A screen-oriented aggregate resource:
GET /v1/coffees/cof_001/detail, designed specifically for that view. Simple and very efficient.
4. What is lost in each case:
- With GraphQL: HTTP caching (everything is a
POSTto/graphql), meaningful status codes and the ability to try the call from the browser; on top of that, query cost has to be controlled. - With the expansion parameter: the cache fragments (each combination of
includeis a separate entry) and the server gets more complicated; if it is overused, you end up reimplementing GraphQL by hand, and worse. - With the aggregate resource: the API becomes coupled to one specific screen. If every new view adds its own endpoint, the API fills up with bespoke resources that are hard to maintain and to document.
There is no free option: all three are conscious trade-offs, and choosing well means knowing which one hurts least in your context.
Conclusion
You now have the complete map of integration styles. REST stands out for its simplicity, its universality and the caching it inherits from HTTP, and it is the natural choice when the consumer could be anyone. GraphQL solves over-fetching and under-fetching by giving the client control over the fields, in exchange for losing HTTP caching and having to govern query cost. gRPC brings a strong contract, a binary format and streaming, and shines between internal services where you control both ends. And webhooks, together with queues, SSE and WebSockets, cover the gap that no request-response model can cover: letting the server take the initiative. The practical conclusion is that they do not compete: Aroma Store will use REST outwards, gRPC inwards and webhooks for its integrations, and it has left GraphQL out for reasons that are written down and revisable.
With this we close module 1. You know what an API is and who it is designed for, where today's ecosystem comes from, how HTTP works underneath, what the six REST constraints consist of, how to measure an API's maturity and what alternatives exist. It is time to move from understanding to building. In module 2, RESTful API Design, we will start designing the Aroma Store API piece by piece: its design principles, its resources and URIs, the precise use of each HTTP method and each status code, content negotiation, filtering and pagination, versioning and documentation. That is the moment when the ideas in this module turn into concrete decisions about a real contract.
REST API Course: Principles of Designing and Developing RESTful APIs
Module 1: Introduction to RESTful APIs
- What Is an API?
- History and Evolution of APIs
- HTTP Fundamentals for APIs
- Basic Principles of REST
- The Richardson Maturity Model and HATEOAS
- REST vs. SOAP
- REST Compared with GraphQL, gRPC and Webhooks
Module 2: Designing RESTful APIs
- RESTful API Design Principles
- Resources and URIs
- HTTP Methods
- HTTP Status Codes
- Representations, Headers and Content Negotiation
- Filtering, Sorting, Pagination and Search
- API Versioning
- API Documentation
Module 3: Building RESTful APIs
- Setting Up the Development Environment
- Building a Basic Server
- Handling Requests and Responses
- Input Data Validation
- Persistence and the Data Access Layer
- Authentication and Authorisation
- Error Handling
- Testing and Validation
Module 4: Best Practices and Security
- API Design Best Practices
- Security in RESTful APIs
- OAuth 2.0 and OpenID Connect in Practice
- Rate Limiting and Throttling
- CORS and Security Policies
- HTTP Caching and Performance
- Observability: Logs, Metrics and Traces
Module 5: Tools and Frameworks
- Postman for API Testing
- Swagger and OpenAPI for Documentation
- Popular Frameworks for RESTful APIs
- Contracts, Mocks and Automated API Testing
- Continuous Integration and Deployment
- API Gateways and Developer Portals
