Course Overview: This course takes you from zero to advanced understanding of API types and networking concepts used in real-world software development. Each lesson builds on the previous one, includes real-world scenarios, and ends with interview Q&A.
| # | Lesson | Level |
|---|---|---|
| 01 | What is an API? Fundamentals & Mental Models | 🟢 Beginner |
| 02 | How the Internet Works — Networking Basics | 🟢 Beginner |
| 03 | HTTP & HTTPS — The Language of the Web | 🟢 Beginner |
| 04 | REST APIs — The Industry Standard | 🟡 Intermediate |
| 05 | GraphQL APIs — Query What You Need | 🟡 Intermediate |
| 06 | SOAP APIs — Enterprise-Grade Web Services | 🟡 Intermediate |
| 07 | gRPC APIs — High-Performance Communication | 🟡 Intermediate |
| 08 | WebSockets — Real-Time Bidirectional Communication | 🟡 Intermediate |
| 09 | Webhooks — Event-Driven APIs | 🟡 Intermediate |
| 10 | Authentication & Security in APIs | 🟡 Intermediate |
| 11 | API Design Best Practices & Versioning | 🔴 Advanced |
| 12 | Networking Deep Dive — TCP/IP, DNS, CDN | 🔴 Advanced |
| 13 | Microservices & API Gateway Patterns | 🔴 Advanced |
| 14 | Rate Limiting, Caching & Performance | 🔴 Advanced |
| 15 | API Testing, Monitoring & Documentation | 🔴 Advanced |
API stands for Application Programming Interface. It is a contract between two pieces of software that defines:
- What requests can be made
- How to make them
- What responses to expect
Mental Model: Think of a restaurant. You (the client) look at a menu (API documentation), place an order (API request) with the waiter (API), and the kitchen (server) prepares your food (processes data) and sends it back to you (response). You never see the kitchen — you just interact through the waiter.
┌─────────────────────────────────────────────────┐
│ API Architecture │
│ │
│ [Client App] ──request──▶ [API Layer] │
│ │ │
│ [Business Logic] │
│ │ │
│ [Database/Service] │
│ │ │
│ [Client App] ◀──response── [API Layer] │
└─────────────────────────────────────────────────┘
| Type | Protocol | Best For | Example |
|---|---|---|---|
| REST | HTTP | Web & Mobile Apps | Twitter API |
| GraphQL | HTTP | Flexible data fetching | GitHub API v4 |
| SOAP | HTTP/SMTP | Enterprise systems | PayPal (legacy) |
| gRPC | HTTP/2 | Microservices | Google internal |
| WebSocket | WS/WSS | Real-time apps | Slack, Chat |
| Webhook | HTTP callback | Event notifications | Stripe payments |
Scenario: You're building a weather app.
- Your app calls the OpenWeatherMap REST API with a city name
- The API returns JSON with temperature, humidity, and forecast
- Your app displays this to users
Without the API, you'd need to build your own weather satellites and data infrastructure. APIs let you leverage existing services.
- Endpoint: A specific URL where an API can be accessed (e.g.,
/api/users) - Request: Data sent from client to server
- Response: Data returned from server to client
- Payload: The body/data of a request or response
- SDK: Software Development Kit — a wrapper around an API for easier use
Q1: What is an API and why is it important in software development?
A: An API is a defined interface that allows different software components to communicate with each other. It's important because it enables modularity (teams can work independently), reusability (use existing services), abstraction (hide implementation details), and scalability (distribute functionality across services). For example, instead of building payment processing from scratch, a developer integrates Stripe's API.
Q2: What is the difference between an API and a web service?
A: All web services are APIs, but not all APIs are web services. An API is a broad concept — it can be a library API (like Python's
osmodule), operating system API, or web API. A web service is specifically an API accessible over a network using web protocols like HTTP. REST, SOAP, and GraphQL are all web services.
Q3: Can you name some real-world applications where APIs are critical?
A:
- Payment gateways (Stripe, PayPal) — process transactions
- Social login (Google OAuth, Facebook Login) — authentication
- Maps (Google Maps API) — location services in Uber, Airbnb
- SMS/Email (Twilio, SendGrid) — notifications
- Cloud storage (AWS S3 API) — file uploads
- AI capabilities (OpenAI API) — chatbots, language models
Q4: What is the difference between public, private, and partner APIs?
A:
- Public API: Open to any developer (e.g., OpenWeatherMap, GitHub API). May require registration.
- Private API: Internal to an organization, used between internal services (e.g., a company's internal user service API).
- Partner API: Shared with specific business partners under agreements (e.g., shipping company's API shared only with e-commerce partners).
Before building APIs, you need to understand the infrastructure they run on. The internet is a global network of computers communicating via standardized protocols.
┌──────────────────────────────────────────┐
│ OSI Model — 7 Layers │
│ │
│ 7. Application ← HTTP, FTP, DNS │
│ 6. Presentation ← Encryption, Encoding │
│ 5. Session ← Connection mgmt │
│ 4. Transport ← TCP, UDP (ports) │
│ 3. Network ← IP Addressing │
│ 2. Data Link ← MAC Addresses │
│ 1. Physical ← Cables, WiFi │
└──────────────────────────────────────────┘
For API developers, focus on layers 3 (Network), 4 (Transport), and 7 (Application).
Every device on the internet has an IP address — a unique identifier.
IPv4: 192.168.1.1 (32-bit, ~4.3 billion addresses)
IPv6: 2001:0db8:85a3:: (128-bit, virtually unlimited)
- Public IP: Visible on the internet (your router's IP)
- Private IP: Internal network (192.168.x.x)
- Localhost:
127.0.0.1— your own machine
Ports are like doors on a server. Each service listens on a specific port:
| Port | Service |
|---|---|
| 80 | HTTP |
| 443 | HTTPS |
| 22 | SSH |
| 3306 | MySQL |
| 5432 | PostgreSQL |
| 3000 | Node.js (dev) |
| 8080 | HTTP alt / Dev servers |
| 6379 | Redis |
DNS translates human-readable domain names to IP addresses.
User types: www.google.com
↓
DNS Query: "What is the IP for google.com?"
↓
DNS Response: 142.250.74.46
↓
Browser connects to: 142.250.74.46:443
DNS Resolution Steps:
- Check browser cache
- Check OS cache (
/etc/hosts) - Query local DNS resolver (ISP)
- Query root nameservers
- Query TLD nameservers (
.com) - Query authoritative nameserver for
google.com - Return IP address
| Feature | TCP | UDP |
|---|---|---|
| Connection | Connection-oriented (handshake) | Connectionless |
| Reliability | Guaranteed delivery | Best-effort |
| Order | Ordered packets | No ordering |
| Speed | Slower | Faster |
| Use Case | HTTP, APIs, file transfer | Video streaming, gaming, DNS |
TCP 3-Way Handshake:
Client → SYN → Server (I want to connect)
Client ← SYN-ACK ← Server (OK, I'm ready)
Client → ACK → Server (Great, let's communicate)
Scenario: You deploy a Node.js REST API to AWS.
- Your app runs on port
3000internally - Nginx reverse proxy listens on port
80/443and forwards to3000 - DNS points
api.yourcompany.comto AWS Elastic IP - TCP ensures all API request/response packets arrive reliably
Q1: What happens when you type https://api.github.com/users in a browser?
A:
- Browser checks DNS cache, then queries DNS for
api.github.com- DNS returns GitHub's IP address
- Browser initiates TCP connection (3-way handshake) on port 443
- TLS/SSL handshake occurs for HTTPS encryption
- Browser sends HTTP GET request
- GitHub's server processes and returns JSON response
- Browser receives and displays data
Q2: What is the difference between TCP and UDP and when would you use each?
A: TCP is reliable, ordered, and connection-based — ideal for APIs, web requests, and any data where completeness matters. UDP is faster and connectionless — ideal for real-time applications like video calls, online gaming, or live streaming where a dropped packet is acceptable but latency is critical. DNS also uses UDP for initial queries due to speed.
Q3: What is a reverse proxy and why is it used with APIs?
A: A reverse proxy (e.g., Nginx, HAProxy) sits in front of your API servers and handles incoming requests. Benefits include: load balancing across multiple servers, SSL termination, caching, compression, rate limiting, and hiding internal server architecture. When you call
api.example.com, you're likely hitting a reverse proxy that forwards to multiple backend servers.
Q4: What is NAT (Network Address Translation)?
A: NAT allows multiple devices on a private network to share a single public IP address. Your router uses NAT — all devices in your home appear to the internet as one IP. For APIs, this matters because
X-Forwarded-Forheaders are used to pass the real client IP through proxies and NAT layers.
HTTP (HyperText Transfer Protocol) is the foundation of all web APIs. Every REST API, GraphQL endpoint, and webhook uses HTTP.
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGci...
Accept: application/json
{
"name": "Ahmed Khan",
"email": "ahmed@example.com"
}Parts:
- Method:
POST(the action) - Path:
/api/users(the resource) - HTTP Version:
HTTP/1.1 - Headers: Metadata about the request
- Body: The payload (data being sent)
HTTP/1.1 201 Created
Content-Type: application/json
X-Request-Id: abc123
{
"id": 42,
"name": "Ahmed Khan",
"email": "ahmed@example.com",
"createdAt": "2025-01-15T10:30:00Z"
}| Method | Action | Idempotent | Body |
|---|---|---|---|
| GET | Read/Retrieve | ✅ Yes | ❌ No |
| POST | Create | ❌ No | ✅ Yes |
| PUT | Replace (full update) | ✅ Yes | ✅ Yes |
| PATCH | Partial update | ❌ No | ✅ Yes |
| DELETE | Remove | ✅ Yes | Optional |
| HEAD | Like GET, no body | ✅ Yes | ❌ No |
| OPTIONS | CORS preflight | ✅ Yes | ❌ No |
Idempotent means calling the endpoint multiple times produces the same result. DELETE
/users/1ten times has the same effect as once — the user is deleted.
1xx — Informational
100 Continue
2xx — Success
200 OK
201 Created
204 No Content
3xx — Redirection
301 Moved Permanently
302 Found (Temporary)
304 Not Modified (cached)
4xx — Client Errors
400 Bad Request
401 Unauthorized (not logged in)
403 Forbidden (logged in, no permission)
404 Not Found
409 Conflict
422 Unprocessable Entity
429 Too Many Requests
5xx — Server Errors
500 Internal Server Error
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
HTTPS = HTTP + TLS (Transport Layer Security). TLS provides:
- Encryption: Data is unreadable in transit
- Authentication: Server proves its identity via certificate
- Integrity: Data cannot be tampered with
HTTP → Data travels as plaintext (❌ Never use for APIs)
HTTPS → Data is encrypted with TLS (✅ Always use in production)
# Request Headers
Authorization: Bearer {token} ← Authentication
Content-Type: application/json ← Format of body sent
Accept: application/json ← Format you want back
X-API-Key: abc123 ← API key auth
X-Request-ID: uuid-here ← Tracing
Cache-Control: no-cache ← Caching directives
Origin: https://myapp.com ← CORS
# Response Headers
Content-Type: application/json
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1705312800
ETag: "abc123" ← Cache validation
Access-Control-Allow-Origin: * ← CORSScenario: Building a login system.
POST /auth/loginwith{email, password}→ returns200 OKwith JWT tokenGET /profilewithAuthorization: Bearer {token}→ returns200 OKwith user data- Wrong credentials →
401 Unauthorized - Accessing admin endpoint as regular user →
403 Forbidden - Sending malformed JSON →
400 Bad Request
Q1: What is the difference between 401 and 403?
A:
401 Unauthorizedmeans the client is not authenticated — they haven't provided credentials or the credentials are invalid (not logged in).403 Forbiddenmeans the client IS authenticated but doesn't have permission to access that resource (logged in but lacks required role/permission). Example: Accessing/adminwhen you're logged in as a regular user returns 403.
Q2: What is the difference between PUT and PATCH?
A:
PUTreplaces the entire resource. If you PUT{name: "Ali"}to a user with{name: "Ahmed", age: 25}, you'll get{name: "Ali"}— age is gone.PATCHpartially updates — PATCHing{name: "Ali"}preserves age and only updates the name. In practice, most APIs use PATCH for updates and PUT for full replacements.
Q3: What is CORS and why does it matter for APIs?
A: Cross-Origin Resource Sharing (CORS) is a browser security mechanism that blocks web pages from making requests to a different domain than the one that served the page. If your frontend is at
app.example.comand your API is atapi.example.com, the browser will block requests unless the API sendsAccess-Control-Allow-Originheaders. The browser sends anOPTIONSpreflight request to check permissions before the actual request. APIs must be configured to allow specific origins.
Q4: What is HTTP/2 and how is it different from HTTP/1.1?
A: HTTP/2 introduced multiplexing (multiple requests over one connection simultaneously), header compression (HPACK), server push, and binary framing. HTTP/1.1 sends one request per connection at a time (or uses workarounds like connection pooling). HTTP/2 significantly reduces latency, especially for APIs making many parallel requests. gRPC is built on HTTP/2 to take advantage of these features.
REST (Representational State Transfer) is an architectural style (not a protocol) for designing networked APIs. Coined by Roy Fielding in 2000, it defines six constraints that make APIs scalable and maintainable.
- Client-Server: Separation of concerns — client handles UI, server handles data
- Stateless: Each request contains all information needed; server stores no session state
- Cacheable: Responses should indicate if they can be cached
- Uniform Interface: Consistent resource naming and HTTP verbs
- Layered System: Client doesn't know if connected directly or through proxies
- Code on Demand (optional): Server can send executable code to client
REST is built around resources (nouns, not verbs):
✅ CORRECT (REST) ❌ WRONG (RPC-style)
GET /users GET /getUsers
GET /users/42 GET /getUserById?id=42
POST /users POST /createUser
PUT /users/42 POST /updateUser/42
DELETE /users/42 GET /deleteUser?id=42
# Nested resources
GET /users/42/orders All orders for user 42
GET /users/42/orders/7 Order 7 of user 42
POST /users/42/orders Create order for user 42
Scenario: E-commerce Product API
# Products
GET /api/v1/products → List all products
GET /api/v1/products?category=phones&limit=10 → Filtered list
GET /api/v1/products/123 → Get product 123
POST /api/v1/products → Create new product
PUT /api/v1/products/123 → Replace product 123
PATCH /api/v1/products/123 → Update product 123
DELETE /api/v1/products/123 → Delete product 123
# Nested: Reviews for a product
GET /api/v1/products/123/reviews → All reviews for product 123
POST /api/v1/products/123/reviews → Add review to product 123
Create a user:
POST /api/v1/users
Content-Type: application/json
{
"name": "Fatima Malik",
"email": "fatima@example.com",
"role": "customer"
}HTTP/1.1 201 Created
Location: /api/v1/users/99
{
"id": 99,
"name": "Fatima Malik",
"email": "fatima@example.com",
"role": "customer",
"createdAt": "2025-01-15T10:00:00Z"
}Paginated list:
GET /api/v1/products?page=2&limit=20&sort=price&order=asc{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 450,
"totalPages": 23,
"hasNext": true,
"hasPrev": true
}
}Scenario: Building a Task Management API (like Trello)
GET /boards → User's boards
POST /boards → Create board
GET /boards/1/lists → Lists in board 1
POST /boards/1/lists → Add list to board 1
GET /boards/1/lists/3/cards → Cards in list 3
POST /boards/1/lists/3/cards → Create card
PATCH /boards/1/lists/3/cards/7 → Update card (e.g., move, rename)
DELETE /boards/1/lists/3/cards/7 → Delete card
Q1: What makes an API truly RESTful vs just using HTTP?
A: A RESTful API follows Roy Fielding's constraints: statelessness (no server-side sessions), uniform interface (consistent resource naming with HTTP verbs), client-server separation, cacheability, and a layered system. Many APIs call themselves REST but don't follow all constraints — they're called REST-like or HTTP APIs. True REST also uses HATEOAS (Hypermedia as the Engine of Application State) where responses include links to related actions, though few APIs implement this.
Q2: What is HATEOAS and do modern APIs use it?
A: HATEOAS means API responses include hyperlinks to related actions/resources. For example, a user response would include
"_links": {"self": "/users/1", "orders": "/users/1/orders", "delete": "/users/1"}. It makes APIs self-discoverable. In theory it's ideal, in practice most APIs skip it because it adds complexity and clients often already know the API structure. GitHub's API partially implements it.
Q3: How would you design a REST API for a social media "like" feature?
A: Since "like" is an action on a resource, model it as a sub-resource:
POST /posts/42/likes→ Like post 42 (creates the like)DELETE /posts/42/likes/{userId}→ Unlike post 42GET /posts/42/likes→ Get all users who liked post 42GET /posts/42/likes/count→ Get like count This keeps the URL noun-based while the HTTP verb expresses the action.
Q4: What is the Richardson Maturity Model?
A: It's a model with 4 levels measuring how RESTful an API is:
- Level 0: One endpoint, uses HTTP as transport (like SOAP over HTTP)
- Level 1: Multiple resources/endpoints, but only one HTTP verb
- Level 2: Proper HTTP verbs (GET, POST, DELETE) used correctly
- Level 3: HATEOAS — responses include links to related actions Most production APIs are at Level 2. Level 3 is the theoretical ideal.
GraphQL is a query language for APIs developed by Facebook (2015). Instead of multiple REST endpoints, GraphQL exposes a single endpoint where clients specify exactly what data they need.
REST Problem — Over-fetching:
GET /users/42
Returns: id, name, email, address, phone, preferences, metadata...
(You only needed name and email)
REST Problem — Under-fetching:
GET /users/42 → get user
GET /users/42/posts → get their posts
GET /users/42/friends → get their friends
(3 round trips for one screen)
GraphQL Solution — One query, exact data:
{
user(id: 42) {
name
email
posts(limit: 3) {
title
createdAt
}
friends(limit: 5) {
name
avatar
}
}
}
Schema Definition (SDL):
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
body: String!
author: User!
tags: [String!]!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
type Subscription {
newPost: Post!
userStatusChanged(userId: ID!): User!
}Query:
query GetUserProfile {
user(id: "42") {
name
email
posts(limit: 3) {
id
title
tags
}
}
}Mutation (Create):
mutation CreateNewUser {
createUser(input: { name: "Omar Sheikh", email: "omar@example.com" }) {
id
name
createdAt
}
}Subscription (Real-time):
subscription OnNewPost {
newPost {
id
title
author {
name
}
}
}| Concept | Description |
|---|---|
| Schema | Defines all types, queries, mutations |
| Resolver | Function that fetches data for each field |
| Query | Read data |
| Mutation | Write/modify data |
| Subscription | Real-time data via WebSocket |
| Fragment | Reusable field selections |
| Variables | Parameterize queries |
| Introspection | Query the schema itself |
Scenario: GitHub uses GraphQL for its v4 API. Their REST v3 API required many requests to build a repository page. With GraphQL:
query GitHubRepoPage($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
name
description
stargazerCount
forkCount
issues(first: 10, states: OPEN) {
nodes {
title
author {
login
}
createdAt
}
}
pullRequests(first: 5, states: OPEN) {
nodes {
title
headRefName
}
}
}
}One request replaces what would have been 4+ REST calls.
Q1: What are the advantages and disadvantages of GraphQL over REST?
A: Advantages: No over/under-fetching, single endpoint, strongly typed schema, self-documenting (introspection), great for complex/nested data, clients control response shape, real-time via subscriptions. Disadvantages: Complex to set up, caching is harder (all POST to one endpoint), N+1 query problem in resolvers, overkill for simple CRUD, learning curve, rate limiting is trickier. Best suited for complex applications with diverse clients (mobile, web, desktop).
Q2: What is the N+1 problem in GraphQL and how do you solve it?
A: If you query 10 users with their posts, GraphQL might run 1 query for users and then 10 separate queries for each user's posts — 11 total. This is N+1. The solution is DataLoader (Facebook's library) which batches and caches requests: instead of 10 individual
SELECT * FROM posts WHERE user_id = ?calls, it fires oneSELECT * FROM posts WHERE user_id IN (1,2,...,10).
Q3: How does GraphQL handle versioning compared to REST?
A: GraphQL doesn't need versioning in the traditional sense. REST requires
/v1/,/v2/endpoints when the API changes. In GraphQL, you simply add new fields/types (backward compatible) and mark old ones with@deprecateddirective. Clients that don't request deprecated fields are unaffected. The schema evolves without breaking existing clients. This is one of GraphQL's major advantages.
Q4: When would you choose REST over GraphQL?
A: Choose REST when: the API is simple CRUD with limited data relationships, public API where caching is critical (REST is easier to cache with GET requests), team is smaller/less experienced with GraphQL, microservice-to-microservice communication (where gRPC or REST is simpler), file uploads are needed (GraphQL handles them awkwardly), or you need strict HTTP method semantics and status codes.
SOAP (Simple Object Access Protocol) is a protocol (not just an architectural style) for exchanging structured information in web services. It uses XML exclusively and is highly formal with strict contracts.
| Aspect | SOAP | REST |
|---|---|---|
| Protocol | Protocol (strict) | Architectural style |
| Format | XML only | JSON, XML, etc. |
| Contract | WSDL (required) | OpenAPI (optional) |
| Transport | HTTP, SMTP, JMS | HTTP only |
| Security | WS-Security built-in | OAuth/JWT |
| Error handling | Fault elements | HTTP status codes |
| Use case | Enterprise, banking | Web/mobile apps |
| Verbosity | Very verbose | Lightweight |
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:tns="http://example.com/bankservice">
<soap:Header>
<tns:Authentication>
<tns:Username>user123</tns:Username>
<tns:Password>pass456</tns:Password>
</tns:Authentication>
</soap:Header>
<soap:Body>
<tns:TransferFunds>
<tns:FromAccount>ACC001</tns:FromAccount>
<tns:ToAccount>ACC002</tns:ToAccount>
<tns:Amount>5000.00</tns:Amount>
<tns:Currency>PKR</tns:Currency>
</tns:TransferFunds>
</soap:Body>
</soap:Envelope>SOAP Fault (Error) Response:
<soap:Envelope>
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Insufficient funds</faultstring>
<detail>
<tns:ErrorDetail>
<tns:Code>E001</tns:Code>
<tns:Message>Account balance too low for transfer</tns:Message>
</tns:ErrorDetail>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>SOAP is still heavily used in:
- Banking systems: Inter-bank transfers, SWIFT integrations
- Government APIs: Tax filing, national ID verification
- Healthcare: HL7 FHIR (some implementations), insurance claims
- ERP systems: SAP, Oracle integrations
- Legacy enterprise systems: Airlines, insurance companies
Example: Pakistan's FBR (Federal Board of Revenue) e-filing system uses SOAP-based web services for tax submissions.
Q1: Why is SOAP still used in enterprise systems despite REST being simpler?
A: SOAP persists because: it has built-in security standards (WS-Security, WS-Trust), supports ACID-compliant transactions across services, provides formal contracts via WSDL that generate code automatically, works over multiple protocols (not just HTTP), and many enterprise systems were built decades ago and can't be easily migrated. Banking and government systems also value SOAP's strict typing and formal validation.
Q2: What is WSDL?
A: WSDL (Web Services Description Language) is an XML document that describes a SOAP web service — what operations are available, what parameters they take, what they return, and where the service is located. It's the formal contract of a SOAP service. IDEs and tools can import WSDL to auto-generate client code (stubs), which is why enterprise teams value it for type safety across team boundaries.
Q3: What are WS-* standards?
A: WS-* (Web Services standards) are extensions built on top of SOAP for enterprise features: WS-Security (message-level encryption/signing), WS-ReliableMessaging (guaranteed delivery), WS-Transaction (distributed transactions), WS-Coordination, and WS-Policy. These make SOAP more feature-rich than REST for complex enterprise scenarios but also significantly more complex to implement.
gRPC (Google Remote Procedure Call) is a modern, high-performance RPC framework by Google. It uses Protocol Buffers (protobuf) for serialization and HTTP/2 for transport.
REST/JSON:
{"userId": 42, "name": "Ahmed", "email": "ahmed@example.com"}
Size: 58 bytes (text)
gRPC/Protobuf:
Binary encoding: ~15 bytes (binary, compressed)
Speed: ~7x faster serialization than JSON
Define your schema in .proto file:
syntax = "proto3";
package userservice;
service UserService {
rpc GetUser (GetUserRequest) returns (UserResponse);
rpc CreateUser (CreateUserRequest) returns (UserResponse);
rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
rpc StreamUsers (stream UserEvent) returns (stream UserResponse);
}
message GetUserRequest {
int64 user_id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
string role = 3;
}
message UserResponse {
int64 id = 1;
string name = 2;
string email = 3;
string role = 4;
int64 created_at = 5;
}
message ListUsersRequest {
int32 page = 1;
int32 limit = 2;
}
message UserEvent {
string event_type = 1;
int64 user_id = 2;
}| Pattern | Description | Use Case |
|---|---|---|
| Unary | Single request, single response | GetUser, CreateUser |
| Server Streaming | One request, stream of responses | File download, live feed |
| Client Streaming | Stream of requests, one response | File upload, bulk insert |
| Bidirectional Streaming | Stream in both directions | Chat, real-time gaming |
Scenario: Ride-sharing app microservices
Client App
↓ REST/JSON (for mobile)
API Gateway
↓ gRPC (internal, high-speed)
├── UserService.proto
├── DriverService.proto
├── LocationService.proto (bidirectional streaming for live tracking)
├── PricingService.proto
└── PaymentService.proto
Uber, Netflix, and Google use gRPC internally because the performance gains matter at scale.
Q1: When would you choose gRPC over REST?
A: Choose gRPC for: microservice-to-microservice communication (internal APIs), when performance and bandwidth are critical (mobile networks, high-throughput systems), polyglot environments (auto-generates clients in 10+ languages from .proto), real-time streaming needs, and when strongly-typed contracts between services are important. Avoid gRPC for: public APIs (less browser-friendly), when you need human-readable requests for debugging, and when simplicity matters more than performance.
Q2: What are Protocol Buffers and why are they faster than JSON?
A: Protocol Buffers (protobuf) is Google's binary serialization format. It's faster than JSON because: it uses binary encoding (no text parsing), field names are replaced with small integer identifiers (no repeated string keys), the schema is pre-defined (no need to discover structure at runtime), and the binary format is more compact. Serialization/deserialization is typically 3–10x faster and payload size is 2–5x smaller than equivalent JSON.
Q3: What is the difference between gRPC and REST in terms of browser support?
A: REST over HTTP/1.1 works natively in all browsers. gRPC uses HTTP/2 with binary framing — browsers can't directly call gRPC services because browser
Fetch APIdoesn't expose HTTP/2 trailers (needed by gRPC). The solution is gRPC-Web, a proxy (like Envoy) that translates browser HTTP/1.1 calls to gRPC. This is why gRPC is primarily used for backend-to-backend communication, with REST/GraphQL for client-facing APIs.
WebSockets provide a persistent, full-duplex connection between client and server. Unlike HTTP (request-response), WebSockets allow both sides to send data at any time without a new request.
HTTP (Polling — Inefficient):
Client: "Any new messages?" → Server: "No"
Client: "Any new messages?" → Server: "No"
Client: "Any new messages?" → Server: "Yes! Here: {...}"
(New TCP connection each time — wasteful)
WebSocket (Efficient):
Client: "Open connection"
Server: "Connection open ✅"
[5 minutes later]
Server: "New message! {...}"
[2 minutes later]
Server: "User X is typing..."
[30 seconds later]
Client: "Send this message: {...}"
(Single persistent connection)
# Client initiates upgrade from HTTP to WebSocket
GET /chat HTTP/1.1
Host: ws.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
# Server accepts upgrade
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Server (Node.js):
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Map(); // userId → ws
wss.on("connection", (ws, req) => {
const userId = getUserIdFromToken(req);
clients.set(userId, ws);
console.log(`User ${userId} connected`);
ws.on("message", (data) => {
const { type, payload } = JSON.parse(data);
switch (type) {
case "SEND_MESSAGE":
broadcastToRoom(payload.roomId, {
type: "NEW_MESSAGE",
payload: {
text: payload.text,
sender: userId,
timestamp: new Date(),
},
});
break;
case "TYPING":
broadcastToRoom(
payload.roomId,
{
type: "USER_TYPING",
payload: { userId },
},
[userId],
); // Exclude sender
break;
}
});
ws.on("close", () => {
clients.delete(userId);
console.log(`User ${userId} disconnected`);
});
});Client (Browser):
const ws = new WebSocket("wss://api.example.com/chat");
ws.onopen = () => {
console.log("Connected to server");
ws.send(
JSON.stringify({ type: "AUTH", token: localStorage.getItem("token") }),
);
};
ws.onmessage = (event) => {
const { type, payload } = JSON.parse(event.data);
if (type === "NEW_MESSAGE") {
displayMessage(payload);
} else if (type === "USER_TYPING") {
showTypingIndicator(payload.userId);
}
};
ws.onclose = (event) => {
console.log("Disconnected. Reconnecting...");
setTimeout(connectWebSocket, 3000); // Auto-reconnect
};
ws.onerror = (error) => console.error("WebSocket error:", error);
// Send a message
function sendMessage(text, roomId) {
ws.send(
JSON.stringify({
type: "SEND_MESSAGE",
payload: { text, roomId },
}),
);
}| App | WebSocket Use |
|---|---|
| Slack / WhatsApp | Real-time messaging |
| Google Docs | Collaborative editing |
| Binance / Trading | Live price updates |
| Uber | Driver location tracking |
| Online games | Multiplayer state sync |
| Live sports | Score updates |
Q1: When should you use WebSockets vs REST polling vs Server-Sent Events?
A:
- WebSockets: Full bidirectional, real-time. Use for chat, multiplayer games, collaborative tools, live trading.
- Server-Sent Events (SSE): One-way server → client, simpler to implement, works over HTTP. Use for live feeds, notifications, progress updates where client doesn't need to send data frequently.
- Long Polling: Client sends request, server holds it until data available. Old technique, higher overhead. Use when WebSockets aren't supported.
- Short Polling: Regular interval requests. Simple but wasteful. Use only for infrequently updating data with simple requirements.
Q2: How do you handle WebSocket reconnection in production?
A: Implement exponential backoff: try reconnecting at 1s, then 2s, 4s, 8s, up to a maximum (e.g., 30s). Track a reconnection attempt counter. On reconnect, re-authenticate (re-send auth token), rejoin rooms/channels, and request any missed messages using a timestamp/sequence number. Libraries like
reconnecting-websockethandle this automatically.
Q3: How do WebSockets scale across multiple servers?
A: WebSockets maintain a persistent connection to a specific server. If you have 3 servers, clients on server A can't directly receive messages from clients on server B. Solution: use a pub/sub broker (Redis Pub/Sub, Apache Kafka) where each server publishes and subscribes to channels. When user on server A sends a message, it publishes to Redis channel, all servers receive it and forward to their connected clients. This is how Slack and WhatsApp scale.
Webhooks are HTTP callbacks — instead of your app polling an API for updates, the external service pushes data to your endpoint when an event occurs.
Polling (Inefficient):
Your App → "Did payment succeed?" → Stripe
Your App → "Did payment succeed?" → Stripe (every 5 seconds)
Your App → "Did payment succeed?" → Stripe
Webhook (Efficient):
[Payment succeeds]
Stripe → POST your-app.com/webhooks/stripe → Your App
Your App processes the event
Receiving a Stripe Webhook:
const express = require("express");
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
app.post(
"/webhooks/stripe",
express.raw({ type: "application/json" }), // Raw body for signature verification
async (req, res) => {
const sig = req.headers["stripe-signature"];
let event;
try {
// Verify webhook signature (CRITICAL for security)
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET,
);
} catch (err) {
console.error(`Signature verification failed: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case "payment_intent.succeeded":
const paymentIntent = event.data.object;
await fulfillOrder(paymentIntent.metadata.orderId);
await sendConfirmationEmail(paymentIntent.metadata.customerEmail);
break;
case "payment_intent.payment_failed":
const failedPayment = event.data.object;
await notifyPaymentFailed(failedPayment.metadata.orderId);
break;
case "customer.subscription.deleted":
const subscription = event.data.object;
await deactivateUserSubscription(subscription.customer);
break;
}
// Must return 200 quickly (Stripe retries if no response in 30s)
res.json({ received: true });
},
);Always verify webhook signatures to prevent fake payloads:
// GitHub webhook signature verification
const crypto = require("crypto");
function verifyGitHubWebhook(req, secret) {
const signature = req.headers["x-hub-signature-256"];
const hmac = crypto.createHmac("sha256", secret);
const digest = "sha256=" + hmac.update(req.rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}| Service | Event | Your Action |
|---|---|---|
| Stripe | payment_intent.succeeded |
Fulfill order, send receipt |
| GitHub | push |
Trigger CI/CD pipeline |
| Twilio | message.received |
Process incoming SMS |
| Shopify | order.created |
Update inventory |
| SendGrid | email.bounced |
Mark email as invalid |
| Slack | app_mention |
Respond to bot message |
Q1: What challenges come with receiving webhooks in production?
A:
- Idempotency: Webhooks can be delivered more than once (retries). Process by storing event IDs and skipping duplicates.
- Response time: Must respond with 200 quickly (< 5–30s). Offload heavy processing to a queue (Redis, RabbitMQ).
- Failures: Your server might be down when webhook fires. Providers retry, so implement proper idempotency.
- Security: Always verify signatures.
- Ordering: Events may arrive out of order. Don't assume event sequence.
- Debugging: Use tools like ngrok for local testing, and providers like Stripe's webhook dashboard for replay.
Q2: How do you handle webhook delivery failures?
A: Design your endpoint to be idempotent (same event processed twice = same result). Store incoming events in a database first with a status of "pending". A background worker processes them. If processing fails, retry with exponential backoff. Log all incoming webhooks before processing for debugging. Use a dead letter queue for events that fail after all retries.
API security ensures only authorized users/apps can access your endpoints. It covers authentication (who are you?) and authorization (what can you do?).
1. API Keys
GET /api/data
X-API-Key: sk_live_abc123xyz456- Simple, stateless
- Hard to revoke specific permissions
- Use for: machine-to-machine, public APIs
2. Basic Authentication
GET /api/data
Authorization: Basic dXNlcjpwYXNz ← base64("user:password")- Only use over HTTPS
- Credentials sent every request
- Use for: internal tools, simple APIs
3. Bearer Token (JWT)
GET /api/data
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...JWT Structure:
Header.Payload.Signature
eyJhbGci... . eyJ1c2VySWQiOjQy... . SflKxwRJSMeKKF2QT4...
Decoded Payload:
{
"userId": 42,
"email": "user@example.com",
"role": "admin",
"iat": 1705312800, ← Issued at
"exp": 1705316400 ← Expires at (1 hour)
}
4. OAuth 2.0 Flows
Authorization Code Flow (Web Apps):
User → Your App → Redirect to Google
Google → User enters credentials
Google → Redirect back with code
Your App → Exchange code for token
Your App → Use token for API calls
Client Credentials Flow (Machine-to-Machine):
Your Service → POST /oauth/token {client_id, client_secret}
Auth Server → Returns access_token
Your Service → Calls API with token
✅ Always use HTTPS
✅ Validate and sanitize all inputs
✅ Use parameterized queries (prevent SQL injection)
✅ Implement rate limiting
✅ Short-lived tokens (15min access, 7 days refresh)
✅ Rotate API keys regularly
✅ Log all authentication events
✅ Use CORS properly (don't use wildcard * in production)
✅ Validate Content-Type headers
✅ Implement request size limits
❌ Never store passwords in plaintext
❌ Never log credentials or tokens
❌ Never put secrets in URLs (visible in server logs)
❌ Never trust client-side validation alone
Scenario: E-commerce API Security
Public endpoints (no auth):
GET /products
GET /products/:id
GET /categories
Authenticated (JWT required):
GET /profile
POST /orders
GET /orders/:id
Admin only (JWT + admin role):
POST /products
DELETE /products/:id
GET /admin/users
Service-to-service (OAuth client credentials):
POST /internal/inventory/reserve
POST /internal/emails/send
Q1: What is the difference between authentication and authorization?
A: Authentication is verifying identity — "Who are you?" (login with credentials). Authorization is verifying permissions — "What are you allowed to do?" (can this user access this resource?). In APIs: authentication is handled by JWT/OAuth (verifying the token is valid and identifying the user), authorization is handled by role checks or permission scopes (verifying the user has access to the specific endpoint or data).
Q2: What is a JWT and what are its security considerations?
A: JWT (JSON Web Token) is a compact, self-contained token with a header, payload, and signature. The server doesn't need to store sessions — it just verifies the signature. Security considerations: use short expiry times (15–60 min) to limit damage if stolen; use refresh tokens for new access tokens; store access tokens in memory (not localStorage — XSS risk); store refresh tokens in HttpOnly cookies (CSRF-protected); always validate the
algfield to prevent thealg:noneattack; use strong secret keys (256+ bits).
Q3: What is OAuth 2.0 and how does it differ from API Keys?
A: OAuth 2.0 is a delegated authorization framework — it lets users grant third-party apps limited access to their accounts without sharing passwords (e.g., "Login with Google"). It has scopes (granular permissions), expiring tokens, and revocable access. API Keys are simpler static credentials for machine-to-machine authentication without user context. OAuth is for user-delegated access; API keys are for direct service authentication.
Q4: How would you protect an API from SQL injection?
A: Never concatenate user input into SQL strings. Always use parameterized queries/prepared statements:
// DANGEROUS db.query(`SELECT * FROM users WHERE id = ${req.params.id}`); // SAFE db.query("SELECT * FROM users WHERE id = ?", [req.params.id]);Also use an ORM (Sequelize, Prisma), validate input types, and implement a WAF (Web Application Firewall) at the infrastructure level.
Good API design is about consistency, predictability, and developer experience (DX). A well-designed API is intuitive, hard to misuse, and easy to evolve.
✅ Use plural nouns for resources
/users /orders /products
✅ Use kebab-case for multi-word resources
/blog-posts /order-items /api-keys
✅ Use consistent query parameter names
?page=1&limit=20&sort=createdAt&order=desc&search=laptop
✅ Use ISO 8601 for dates
"createdAt": "2025-01-15T10:30:00Z"
✅ Use camelCase for JSON fields
{"userId": 42, "firstName": "Ahmed", "createdAt": "..."}
❌ Avoid verbs in URLs
/getUser /createPost /deleteOrder
// Success Response
{
"success": true,
"data": {
"id": 42,
"name": "Ahmed"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"requestId": "req_abc123"
}
}
// List Response
{
"success": true,
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"totalPages": 8
}
}
// Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "age", "message": "Must be 18 or older" }
]
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"requestId": "req_abc123"
}
}1. URL Versioning (Most Common)
/api/v1/users ← Version 1
/api/v2/users ← Version 2 (breaking changes)
✅ Visible, easy to route, cacheable
❌ URL is not "pure" (version isn't a resource)
2. Header Versioning
GET /api/users
API-Version: 2✅ Clean URLs
❌ Harder to test in browser
3. Accept Header (Content Negotiation)
GET /api/users
Accept: application/vnd.company.v2+json✅ Most REST-compliant
❌ Complex, less developer-friendly
Versioning Best Practices:
- Never break existing APIs (add fields, don't remove)
- Deprecate with notice period (6–12 months)
- Add Sunset header when deprecating:
Sunset: Sat, 01 Jun 2026 00:00:00 GMT
- Document migration guides
- Support N-1 versions minimum
Stripe API Versioning: Stripe uses date-based versioning (2023-10-16). Each API key has a default version. When you create a new integration you pin to a version. Stripe maintains backward compatibility for years and deprecates very slowly — a model other companies should follow.
Q1: What is backward compatibility and why is it critical in API design?
A: Backward compatibility means existing clients continue to work after an API update. Breaking changes include: removing fields, renaming fields, changing field types, removing endpoints, changing authentication. Non-breaking: adding new optional fields, adding new endpoints, adding new optional parameters. You can check compatibility by treating the API as a contract — any change that requires clients to update their code is a breaking change and requires a version bump.
Q2: How do you document an API effectively?
A: Use OpenAPI (Swagger) specification for REST APIs — it generates interactive docs, client SDKs, and can validate requests. Good documentation includes: endpoint descriptions, request/response examples, error codes, authentication guide, rate limits, changelog, and a quickstart guide. Tools: Swagger UI, Redoc, Stoplight, Postman documentation. The best docs show code examples in multiple languages and have a live "try it" feature.
Without CDN:
User in Karachi → Request → Origin Server in US (200ms latency)
With CDN:
User in Karachi → Request → CDN Edge in Dubai (20ms latency)
↓ (if not cached)
Origin Server in US
CDN Use Cases for APIs:
- Caching static API responses (product lists, configurations)
- Edge computing (running logic close to users with Cloudflare Workers)
- DDoS protection (absorb traffic before it hits origin)
- SSL termination at edge
| Algorithm | How it Works | Best For |
|---|---|---|
| Round Robin | Rotate through servers | Uniform load |
| Least Connections | Send to server with fewest active connections | Variable request duration |
| IP Hash | Same client → same server | Session persistence |
| Weighted | More powerful servers get more traffic | Mixed hardware |
TLS 1.3 Handshake (Simplified):
Client → ClientHello (supported ciphers, TLS version) → Server
Client ← ServerHello (chosen cipher, certificate) ← Server
Client verifies server certificate with CA
Client → Encrypted session data → Server
[Secure channel established — all data encrypted]
Scenario: High-traffic API Architecture
[Cloudflare CDN/WAF]
↓
[Load Balancer]
/ | \
[API1] [API2] [API3]
\ | /
[Redis Cache]
↓
[PostgreSQL Primary]
[PostgreSQL Replica]
Q1: What is the difference between a load balancer and a reverse proxy?
A: A reverse proxy handles requests on behalf of backend servers — it provides SSL termination, compression, caching, and URL rewriting. A load balancer distributes traffic across multiple backend server instances. In practice, tools like Nginx can act as both simultaneously. A dedicated load balancer (AWS ALB, HAProxy) focuses on traffic distribution with health checking and session persistence. They're often used together: load balancer in front, reverse proxy at each server.
Q2: What is DNS TTL and why does it matter for API deployments?
A: TTL (Time to Live) is how long DNS resolvers cache a record before re-querying. A TTL of 300 (5 minutes) means after changing a DNS record, old IPs may still be served for up to 5 minutes. For deployments: lower TTL before a migration (300s), make the DNS change, wait, then increase TTL again (3600s+). Low TTL increases DNS query load but enables faster failovers.
In microservices architecture, an API Gateway is the single entry point that routes requests to appropriate services, handles cross-cutting concerns, and provides a unified interface.
Client
↓
[API Gateway]
/ | | \
[Auth [User [Order [Product
Service] Service] Service] Service]
↓
[Payment
Service]
API Gateway Responsibilities:
- Request routing
- Authentication/Authorization
- Rate limiting
- SSL termination
- Request/response transformation
- Circuit breaking
- Logging & monitoring
- API aggregation (combine multiple service calls)
Synchronous (Direct):
OrderService → HTTP/gRPC → PaymentService
(Waits for response — coupling)
Asynchronous (Event-driven):
OrderService → Publish "order.created" → Message Queue
↓
PaymentService reads and processes event
(Decoupled — no direct dependency)
Normal: Service A → Service B (healthy)
B fails: Service A → Service B (timeout × 5)
Circuit Opens (stop calling B temporarily)
Fallback: Service A → Returns cached/default data
Recovery: Half-open: Allow 1 test request
If success → Circuit Closes
If fail → Stay open
Netflix Microservices:
- 700+ microservices
- API Gateway (Zuul/Spring Cloud Gateway) routes to services
- Each service owns its data
- Hystrix/Resilience4j handles circuit breaking
- Eureka for service discovery
Q1: What problems does an API Gateway solve?
A: Without an API Gateway, clients must know all service locations, each service implements auth/rate limiting redundantly, cross-service concerns create code duplication, and changing services requires client updates. An API Gateway centralizes: authentication (one place to validate tokens), rate limiting, SSL, logging, routing, and can aggregate multiple service calls into one client response. Clients talk to one URL; internal topology can change freely.
Q2: What is service discovery and why is it needed in microservices?
A: In microservices, service instances start/stop dynamically (containers scaling up/down). Service discovery lets services find each other's addresses dynamically. Client-side discovery: service asks a registry (Consul, Eureka) for addresses and load-balances itself. Server-side discovery: load balancer queries registry and routes (AWS ALB + ECS). Without it, you'd hardcode IPs which breaks when containers restart with new IPs.
1. Fixed Window:
Allow 100 requests per minute.
At :00 counter resets to 0.
Problem: 100 at :59, 100 at :01 = 200 in 2 seconds
2. Sliding Window:
Track requests in last 60 seconds (rolling window).
Better accuracy, higher memory.
3. Token Bucket (Most Common):
Bucket holds 100 tokens.
Each request consumes 1 token.
Tokens refill at 10/second.
Allows bursts up to bucket size.
4. Leaky Bucket:
Requests enter a queue (bucket).
Processed at constant rate (leak).
Smooths traffic, no bursts allowed.
Cache-Aside (Most Common):
1. App checks cache
2. Cache miss → query database
3. Store result in cache
4. Return data
Write-Through:
1. App writes to cache
2. Cache writes to database
3. Consistent but write latency
Write-Behind:
1. App writes to cache
2. Cache writes to database asynchronously
3. Risk of data loss on crash
HTTP Caching Headers:
# Server response
Cache-Control: public, max-age=3600 ← Cache for 1 hour
ETag: "abc123" ← Version identifier
Last-Modified: Wed, 15 Jan 2025 10:00:00 GMT
# Client re-request
If-None-Match: "abc123"
If-Modified-Since: Wed, 15 Jan 2025 10:00:00 GMT
# Server response if unchanged
HTTP/1.1 304 Not Modified ← No body, saves bandwidthTwitter's Caching Strategy:
- Timeline reads are cached in Redis (expensive to recompute)
- Tweet content cached in Memcached
- CDN caches public profiles and images
- Rate limits: 500 tweets/day, 2400 API reads/15 min per app
Q1: What headers does an API return for rate limiting and what do they mean?
A:
X-RateLimit-Limit: 1000— max requests per windowX-RateLimit-Remaining: 743— requests left in current windowX-RateLimit-Reset: 1705316400— Unix timestamp when window resets- When exceeded:
429 Too Many RequestswithRetry-After: 60header (seconds to wait)- Client should implement exponential backoff when hitting 429s.
Q2: What is cache invalidation and why is it called hard?
A: Cache invalidation is deciding when to remove/update cached data when the underlying data changes. It's "hard" because: if you invalidate too eagerly, you lose cache benefits; too lazily, users see stale data. Common strategies: TTL-based expiry (simple but can serve stale data), event-driven invalidation (publish cache-busting events on writes), versioned cache keys (
user:42:v3), and write-through (update cache on every write). Phil Karlton famously said naming and cache invalidation are the two hardest problems in CS.
Unit Tests → Test individual functions/handlers
Integration Tests → Test API endpoints with real DB
Contract Tests → Verify API meets its specification
End-to-End Tests → Full flow from client to database
Load Tests → Performance under concurrent requests
Security Tests → Pen testing, auth bypass attempts
const request = require("supertest");
const app = require("../app");
const db = require("../database");
describe("POST /api/v1/users", () => {
beforeAll(async () => await db.migrate.latest());
afterAll(async () => await db.destroy());
afterEach(async () => await db("users").delete());
it("should create a user with valid data", async () => {
const response = await request(app)
.post("/api/v1/users")
.set("Authorization", "Bearer test_admin_token")
.send({
name: "Ahmed Khan",
email: "ahmed@test.com",
role: "customer",
});
expect(response.status).toBe(201);
expect(response.body.data).toMatchObject({
name: "Ahmed Khan",
email: "ahmed@test.com",
});
expect(response.body.data.id).toBeDefined();
expect(response.body.data.password).toBeUndefined(); // Never expose
});
it("should return 400 for invalid email", async () => {
const response = await request(app)
.post("/api/v1/users")
.send({ name: "Test", email: "not-an-email" });
expect(response.status).toBe(400);
expect(response.body.error.code).toBe("VALIDATION_ERROR");
});
it("should return 409 for duplicate email", async () => {
await request(app)
.post("/api/v1/users")
.send({ ...validUser });
const response = await request(app)
.post("/api/v1/users")
.send({ ...validUser });
expect(response.status).toBe(409);
});
});The Three Pillars:
1. Metrics → Quantitative measurements
- Request rate (req/s)
- Error rate (% of 5xx responses)
- Latency (p50, p95, p99)
- Resource usage (CPU, memory)
2. Logs → Structured event records
- Request/response logs (with requestId)
- Error logs with stack traces
- Audit logs (who did what)
3. Traces → Distributed request tracing
- How long each service took
- Where the bottleneck is
- Full request path through microservices
Key Metrics to Monitor:
Golden Signals (Google SRE):
- Latency: p99 < 500ms for critical endpoints
- Traffic: Requests per second
- Errors: Error rate < 0.1%
- Saturation: CPU < 70%, memory < 80%
| Category | Tools |
|---|---|
| API Design | Postman, Insomnia, Swagger Editor |
| Testing | Jest, Supertest, k6, Artillery |
| Documentation | Swagger UI, Redoc, Slate |
| Monitoring | Datadog, Prometheus + Grafana, New Relic |
| Tracing | Jaeger, Zipkin, AWS X-Ray |
| API Gateway | Kong, AWS API Gateway, Nginx |
| Mocking | WireMock, Mockoon, MSW |
Q1: What is contract testing and how is it different from integration testing?
A: Integration testing verifies your API works correctly internally. Contract testing verifies the API meets a published specification that consumers depend on. Tools like Pact enable consumer-driven contract testing: the consumer defines what they expect, the provider verifies it matches. This is critical in microservices where changing one service can break consumers. Pact runs in CI/CD — if your API change breaks the contract, the pipeline fails before deployment.
Q2: What metrics would you set up for a new production API?
A: Minimum viable monitoring:
- Latency: p50, p95, p99 per endpoint
- Error rate: 4xx vs 5xx rates separately
- Request volume: req/s to detect anomalies
- Uptime/availability: Synthetic monitoring (ping every minute)
- Database query times: Slow query alerts
- Queue depth (if using async processing) Alert when: p99 > 2s, error rate > 1%, uptime < 99.9%. Use dashboards for trends, PagerDuty for incidents.
Q3: How do you design an API for testability?
A: Design for dependency injection — don't hardcode database connections or external services; inject them so tests can substitute mocks. Use factory functions for creating test data. Keep handlers thin (validation → service layer → response) for easier unit testing. Use environment variables for configuration. Provide a test environment with test credentials. Add a
requestIdto every request/response for log correlation. Make side effects (emails, payments) easily mockable.
✅ API fundamentals and mental models
✅ Internet and networking basics (OSI, TCP/IP, DNS)
✅ HTTP/HTTPS protocol deep understanding
✅ REST API design and best practices
✅ GraphQL queries, mutations, subscriptions
✅ SOAP for enterprise scenarios
✅ gRPC and Protocol Buffers
✅ WebSockets for real-time applications
✅ Webhooks for event-driven architecture
✅ API authentication (JWT, OAuth, API Keys)
✅ API versioning and design standards
✅ Advanced networking (CDN, load balancing, TLS)
✅ Microservices and API Gateway patterns
✅ Rate limiting and caching strategies
✅ API testing, monitoring and documentation
| Level | What to Learn Next |
|---|---|
| Intermediate | Build a full REST API with Node.js/Express or FastAPI |
| Intermediate | Implement JWT auth from scratch |
| Intermediate | Deploy an API to AWS/GCP with HTTPS |
| Advanced | Build a GraphQL API with Apollo Server |
| Advanced | Implement gRPC services in Go or Python |
| Advanced | Design a microservices system with an API Gateway |
| Advanced | Set up full observability stack (Prometheus + Grafana) |
- REST: "RESTful Web APIs" by Leonard Richardson
- GraphQL: graphql.org official docs, "The Road to GraphQL"
- gRPC: grpc.io official documentation
- Networking: "Computer Networking: A Top-Down Approach"
- Security: OWASP API Security Top 10 (free online)
- Practice: Build a Postman workspace with all API types
💡 Pro Tip for Interviews: Always connect concepts to real-world scenarios. When asked about REST vs GraphQL, mention GitHub's v3 vs v4 API. When asked about WebSockets, mention how Slack or trading platforms use them. Interviewers value practical understanding over textbook definitions.