15 REST APIs Interview Questions and Answers
Prepare for your next technical interview with this guide on REST APIs, featuring common questions and detailed answers to enhance your understanding.
Prepare for your next technical interview with this guide on REST APIs, featuring common questions and detailed answers to enhance your understanding.
REST APIs (Representational State Transfer Application Programming Interfaces) have become a cornerstone of modern web development. They enable different software systems to communicate over the internet in a standardized way, making it easier to integrate and scale applications. RESTful services are stateless, scalable, and can be used with a variety of data formats, making them a versatile choice for developers.
This article offers a curated selection of interview questions designed to test your understanding and proficiency with REST APIs. By reviewing these questions and their detailed answers, you will be better prepared to demonstrate your knowledge and problem-solving abilities in your upcoming technical interviews.
The PUT and POST methods in REST APIs are used to send data to the server, but they serve different purposes.
PUT Method:
POST Method:
Idempotent operations in REST can be performed multiple times without changing the result beyond the initial application. This ensures repeated requests do not cause unintended side effects, useful in scenarios like network retries.
In REST, the following HTTP methods are idempotent:
Example:
# Example of an idempotent PUT request import requests url = 'https://api.example.com/resource/1' data = {'name': 'example'} response = requests.put(url, json=data) print(response.status_code) # 200 OK # Repeating the same PUT request response = requests.put(url, json=data) print(response.status_code) # 200 OK
Versioning in a REST API can be handled in several ways:
/api/v1/resource
./api/resource?version=1
.X-API-Version: 1
.Accept
header, e.g., Accept: application/vnd.myapi.v1+json
.Each method has its pros and cons. URI versioning is straightforward but can clutter URLs. Query parameters are flexible but less intuitive. Header versioning keeps URLs clean but requires clients to manage headers. Content negotiation is powerful but complex to implement.
Common status codes returned by a REST API and their significance are as follows:
Authentication in a REST API can be implemented using several methods:
A common method is token-based authentication. Here is an example using Python and the Flask framework:
from flask import Flask, request, jsonify import jwt import datetime app = Flask(__name__) app.config['SECRET_KEY'] = 'your_secret_key' @app.route('/login', methods=['POST']) def login(): auth = request.authorization if auth and auth.password == 'password': token = jwt.encode({'user': auth.username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)}, app.config['SECRET_KEY']) return jsonify({'token': token}) return jsonify({'message': 'Could not verify'}), 401 @app.route('/protected', methods=['GET']) def protected(): token = request.headers.get('x-access-tokens') if not token: return jsonify({'message': 'Token is missing'}), 401 try: data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) except: return jsonify({'message': 'Token is invalid'}), 401 return jsonify({'message': 'Protected route', 'user': data['user']}) if __name__ == '__main__': app.run()
CORS (Cross-Origin Resource Sharing) allows restricted resources on a web page to be requested from another domain. It is implemented through a set of HTTP headers that dictate how and when cross-domain requests are allowed.
When a web application makes a request to a different domain, the browser sends an HTTP request with an Origin header. The server responds with specific CORS headers indicating whether the request is allowed. Key headers involved in CORS are:
CORS is important in REST APIs because it helps enforce security policies by ensuring that only authorized domains can access the API.
Rate limiting in a REST API controls the number of requests a client can make to the server within a specified time frame. This prevents abuse, ensures fair usage among clients, and maintains the performance and availability of the API. There are several ways to implement rate limiting, such as using fixed windows, sliding windows, or token buckets.
A common approach is to use middleware to intercept requests and check if the client has exceeded their allowed number of requests. If the limit is exceeded, the server can respond with a status code indicating that the client should slow down their request rate.
Example:
from flask import Flask, request, jsonify from time import time app = Flask(__name__) # Dictionary to store request timestamps request_times = {} # Rate limit configuration RATE_LIMIT = 5 # requests TIME_WINDOW = 60 # seconds @app.route('/api/resource', methods=['GET']) def resource(): client_ip = request.remote_addr current_time = time() if client_ip not in request_times: request_times[client_ip] = [] # Filter out timestamps outside the time window request_times[client_ip] = [t for t in request_times[client_ip] if current_time - t < TIME_WINDOW] if len(request_times[client_ip]) >= RATE_LIMIT: return jsonify({"error": "Rate limit exceeded"}), 429 request_times[client_ip].append(current_time) return jsonify({"message": "Request successful"}) if __name__ == '__main__': app.run()
Pagination in a REST API divides a large dataset into smaller, more manageable chunks, which can be retrieved in separate requests. This improves performance and user experience when dealing with large amounts of data.
Common methods are:
Here is an example of offset-based pagination using Python and Flask:
from flask import Flask, request, jsonify app = Flask(__name__) data = list(range(1, 101)) # Example dataset @app.route('/items', methods=['GET']) def get_items(): try: page = int(request.args.get('page', 1)) per_page = int(request.args.get('per_page', 10)) except ValueError: return jsonify({"error": "Invalid pagination parameters"}), 400 start = (page - 1) * per_page end = start + per_page paginated_data = data[start:end] return jsonify({ "page": page, "per_page": per_page, "total": len(data), "data": paginated_data }) if __name__ == '__main__': app.run(debug=True)
In this example, the get_items
endpoint retrieves the page
and per_page
parameters from the query string, calculates the starting and ending indices, and returns the corresponding subset of the dataset.
Securing a REST API against vulnerabilities like SQL injection and Cross-Site Scripting (XSS) involves several best practices:
Caching in a REST API stores copies of responses to reduce the time and resources required to generate the same response multiple times. It improves performance and scalability by reducing server load and decreasing response time.
Strategies for implementing caching include:
Example:
from flask import Flask, request, jsonify from werkzeug.contrib.cache import SimpleCache app = Flask(__name__) cache = SimpleCache() @app.route('/data') def get_data(): cache_key = 'data' data = cache.get(cache_key) if data is None: # Simulate a slow database query data = {'value': 'This is the data'} cache.set(cache_key, data, timeout=60) # Cache for 60 seconds return jsonify(data) if __name__ == '__main__': app.run(debug=True)
In this example, a simple in-memory cache is used to store the response for a specific endpoint. The cache is checked before processing the request, and if the data is found, it is returned immediately. Otherwise, the data is fetched, stored in the cache, and then returned.
Middleware in REST APIs acts as a bridge between the incoming request and the final request handler. It allows for the processing of requests before they reach the endpoint and responses before they are sent back to the client. Middleware can be used for various purposes such as:
Here is a simple example of middleware in a REST API using Python’s Flask framework:
from flask import Flask, request, jsonify app = Flask(__name__) # Middleware for logging @app.before_request def log_request_info(): print('Headers: %s', request.headers) print('Body: %s', request.get_data()) # Middleware for authentication @app.before_request def authenticate(): token = request.headers.get('Authorization') if not token or token != 'valid-token': return jsonify({'error': 'Unauthorized'}), 401 @app.route('/data', methods=['GET']) def get_data(): return jsonify({'data': 'This is some data'}) if __name__ == '__main__': app.run(debug=True)
In this example, two middleware functions are defined using the @app.before_request
decorator. The first middleware logs the request headers and body, while the second middleware checks for a valid authorization token.
An API gateway is a server that acts as an API front-end, receiving API requests, enforcing throttling and security policies, passing requests to the back-end service, and then passing the response back to the requester. It can also perform various cross-cutting tasks such as authentication, logging, rate limiting, and load balancing.
The benefits of using an API gateway include:
Error handling in REST APIs is important for providing a clear and consistent experience for clients. Here are some best practices:
Documenting a REST API effectively ensures that other developers can understand and use your API without confusion. Here are some best practices:
Testing a REST API involves several strategies to ensure its reliability, performance, and security. Here are some key strategies: