REST APIs handle over 80% of all web service traffic today, yet Roy Fielding's original 2000 dissertation is widely misunderstood. Most "RESTful" APIs violate at least two core constraints. The difference between knowing REST conventions and understanding REST principles is exactly what interviewers probe for.
Table of Contents
- REST Fundamentals Questions
- HTTP Methods Questions
- URL Design Questions
- HTTP Status Codes Questions
- Authentication Questions
- Pagination and Filtering Questions
- API Versioning Questions
- Error Handling Questions
- API Security Questions
- Quick Reference
REST Fundamentals Questions
These questions test your understanding of REST architecture and principles.
What is REST and what makes an API RESTful?
REST stands for Representational State Transfer. It's an architectural style for APIs that uses standard HTTP methods on resources identified by URLs. The core idea is that everything is a resource with a unique URL, and you use standard HTTP methods to perform operations.
A RESTful API should be:
- Stateless - No session state on server, each request is independent
- Resource-based - URLs represent things (nouns), not actions
- Uniform interface - Consistent use of HTTP methods and status codes
- Cacheable - Responses indicate if they can be cached
For example, for a users resource:
GET /users- List all usersGET /users/123- Get specific userPOST /users- Create new userPUT /users/123- Replace userPATCH /users/123- Update user partiallyDELETE /users/123- Delete user
Proper status codes tell clients what happened: 200s for success, 400s for client errors, 500s for server errors.
What is HATEOAS and why is it important?
HATEOAS (Hypermedia As The Engine Of Application State) is a REST constraint where responses include links to related actions and resources. It makes APIs self-documenting and allows clients to navigate without hardcoding URLs.
In practice, few APIs fully implement it, but including pagination links and resource URLs is a good start:
// HATEOAS example
{
"id": 123,
"name": "John",
"email": "john@example.com",
"_links": {
"self": { "href": "/api/users/123" },
"posts": { "href": "/api/users/123/posts" },
"update": { "href": "/api/users/123", "method": "PATCH" },
"delete": { "href": "/api/users/123", "method": "DELETE" }
}
}HTTP Methods Questions
These questions test your knowledge of HTTP verbs and their proper usage.
What are the five main HTTP methods and their purposes?
Each HTTP method has a specific purpose and behavior characteristics:
| Method | Purpose | Safe | Idempotent | Cacheable |
|---|---|---|---|---|
| GET | Retrieve resource(s) | Yes | Yes | Yes |
| POST | Create new resource | No | No | No |
| PUT | Replace entire resource | No | Yes | No |
| PATCH | Partial update | No | Maybe | No |
| DELETE | Remove resource | No | Yes | No |
What is the difference between PUT and PATCH?
PUT replaces the entire resource with the request payload—if you omit a field, it gets removed or set to default. PUT is idempotent, meaning multiple identical requests have the same effect as one. PATCH partially updates a resource—only the fields included in the request are modified, others remain unchanged.
How do you implement CRUD operations with HTTP methods?
Each HTTP method maps to a specific database operation. Here's how to implement them properly with correct status codes:
// GET - Retrieve users
// GET /api/users
// GET /api/users/123
app.get('/api/users/:id?', async (req, res) => {
if (req.params.id) {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
return res.json(user);
}
const users = await User.find();
res.json(users);
});
// POST - Create user
// POST /api/users
// Body: { "name": "John", "email": "john@example.com" }
app.post('/api/users', async (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email required' });
}
const existing = await User.findOne({ email });
if (existing) {
return res.status(409).json({ error: 'Email already exists' });
}
const user = await User.create({ name, email });
res.status(201).json(user); // 201 Created
});
// PUT - Replace entire user
// PUT /api/users/123
// Body: { "name": "John", "email": "john@example.com", "role": "admin" }
app.put('/api/users/:id', async (req, res) => {
const { name, email, role } = req.body;
// PUT requires all fields - replaces entire resource
if (!name || !email) {
return res.status(400).json({ error: 'All fields required for PUT' });
}
const user = await User.findByIdAndUpdate(
req.params.id,
{ name, email, role }, // Complete replacement
{ new: true, overwrite: true }
);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
// PATCH - Partial update
// PATCH /api/users/123
// Body: { "email": "newemail@example.com" }
app.patch('/api/users/:id', async (req, res) => {
// PATCH only updates provided fields
const user = await User.findByIdAndUpdate(
req.params.id,
req.body, // Only update fields in body
{ new: true }
);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
// DELETE - Remove user
// DELETE /api/users/123
app.delete('/api/users/:id', async (req, res) => {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.status(204).send(); // 204 No Content
});URL Design Questions
These questions test your understanding of RESTful resource naming.
What is wrong with verb-based URL design?
REST uses HTTP methods for actions, so URLs should contain nouns (resources), not verbs. Common anti-patterns include using action words in URLs:
# ❌ Bad URL Design
GET /getAllUsers
POST /createNewPost
GET /getPostComments?postId=123
POST /users/123/addComment
DELETE /removeComment/456
Problems:
- Verbs in URLs (REST uses HTTP methods for actions)
- Inconsistent naming
- Actions mixed with resources
How do you design RESTful endpoints for a blog API?
RESTful design uses nouns for URLs and HTTP methods for actions. Here's a properly designed blog API:
# Users
GET /users # List all users
GET /users/123 # Get user 123
POST /users # Create user
PUT /users/123 # Replace user 123
PATCH /users/123 # Update user 123
DELETE /users/123 # Delete user 123
# Posts
GET /posts # List all posts
GET /posts?author=123 # Filter posts by author
GET /posts/456 # Get post 456
POST /posts # Create post
PUT /posts/456 # Replace post
PATCH /posts/456 # Update post
DELETE /posts/456 # Delete post
# Comments (nested under posts)
GET /posts/456/comments # Comments on post 456
GET /posts/456/comments/789 # Specific comment
POST /posts/456/comments # Add comment to post
DELETE /posts/456/comments/789 # Delete comment
# User's posts (alternative access pattern)
GET /users/123/posts # Posts by user 123
Design principles:
- Nouns only in URLs
- Plural resource names
- Hierarchical relationships via nesting
- Query parameters for filtering/sorting
- Consistent patterns throughout
HTTP Status Codes Questions
These questions test your knowledge of proper status code usage.
What are the most important success status codes (2xx)?
Success codes indicate the request was received and processed successfully:
// 200 OK - General success
res.status(200).json(data);
// 201 Created - Resource created successfully
res.status(201).json(newResource);
// 204 No Content - Success but no body (DELETE, some PUTs)
res.status(204).send();What client error codes (4xx) should you know?
Client error codes indicate problems with the request that the client should fix:
// 400 Bad Request - Malformed request, invalid syntax
res.status(400).json({ error: 'Invalid JSON in request body' });
// 401 Unauthorized - Authentication required/failed
res.status(401).json({ error: 'Authentication required' });
// 403 Forbidden - Authenticated but not authorized
res.status(403).json({ error: 'You cannot delete other users\' posts' });
// 404 Not Found - Resource doesn't exist
res.status(404).json({ error: 'User not found' });
// 409 Conflict - Resource state conflict
res.status(409).json({ error: 'Email already registered' });
// 422 Unprocessable Entity - Validation failed
res.status(422).json({
error: 'Validation failed',
details: [
{ field: 'email', message: 'Invalid email format' },
{ field: 'age', message: 'Must be at least 18' }
]
});
// 429 Too Many Requests - Rate limited
res.status(429).json({
error: 'Rate limit exceeded',
retryAfter: 60
});What server error codes (5xx) should you know?
Server error codes indicate problems on the server side:
// 500 Internal Server Error - Unexpected server error
res.status(500).json({ error: 'Internal server error' });
// 503 Service Unavailable - Server overloaded/maintenance
res.status(503).json({
error: 'Service temporarily unavailable',
retryAfter: 300
});When should you use each status code?
| Scenario | Status Code |
|---|---|
| GET succeeds | 200 |
| POST creates resource | 201 |
| DELETE succeeds | 204 |
| Invalid request format | 400 |
| Missing/invalid auth token | 401 |
| Valid auth but no permission | 403 |
| Resource doesn't exist | 404 |
| Duplicate entry | 409 |
| Validation errors | 422 |
| Server crashed | 500 |
Authentication Questions
These questions test your understanding of API security and identity verification.
What is the difference between authentication and authorization?
Authentication verifies WHO you are—proving your identity through credentials like username/password, API keys, or tokens. Authorization determines WHAT you can do—checking if the authenticated user has permission to perform the requested action.
Authentication comes first (401 if failed), then authorization (403 if failed). Example: Logging in authenticates you; checking if you can delete a post authorizes the action.
How do API keys work for authentication?
API keys are simple but less secure—good for server-to-server communication. They're passed in a header and validated on each request:
// Client: Pass API key in header
const response = await fetch('/api/data', {
headers: {
'X-API-Key': 'your-api-key-here'
}
});
// Server: Validate API key
app.use('/api', (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || !isValidApiKey(apiKey)) {
return res.status(401).json({ error: 'Invalid API key' });
}
next();
});How does JWT authentication work?
JWT (JSON Web Token) is a stateless authentication method. The client logs in to get a token, then sends the token with subsequent requests. The server verifies the token without needing session storage:
// Client: Login to get token
const response = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});
const { token } = await response.json();
// Client: Use token for subsequent requests
const data = await fetch('/api/users/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
// Server: Verify token
const jwt = require('jsonwebtoken');
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}How does OAuth 2.0 work?
OAuth 2.0 allows users to grant third-party access without sharing credentials. The flow involves redirecting users to a provider, receiving an authorization code, and exchanging it for an access token:
// OAuth flow (simplified)
// 1. Redirect user to provider
app.get('/auth/google', (req, res) => {
const url = `https://accounts.google.com/oauth/authorize?
client_id=${CLIENT_ID}&
redirect_uri=${REDIRECT_URI}&
response_type=code&
scope=email profile`;
res.redirect(url);
});
// 2. Handle callback with authorization code
app.get('/auth/google/callback', async (req, res) => {
const { code } = req.query;
// Exchange code for access token
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
body: JSON.stringify({
code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uri: REDIRECT_URI,
grant_type: 'authorization_code'
})
});
const { access_token } = await tokenResponse.json();
// Use access token to get user info
const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: { Authorization: `Bearer ${access_token}` }
});
const userData = await userResponse.json();
// Create session or JWT for your app
});Pagination and Filtering Questions
These questions test your knowledge of handling large datasets in APIs.
How do you implement pagination in a REST API?
Pagination prevents returning thousands of records in a single response. Use query parameters for page number and limit, and include metadata in the response:
// GET /api/posts?page=2&limit=20
app.get('/api/posts', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const [posts, total] = await Promise.all([
Post.find().skip(skip).limit(limit),
Post.countDocuments()
]);
res.json({
data: posts,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1
}
});
});How do you implement filtering and sorting?
Use query parameters for filtering and sorting. A common convention is using a - prefix for descending order:
// GET /api/posts?status=published&author=123&sort=-createdAt,title
app.get('/api/posts', async (req, res) => {
const { status, author, sort, search } = req.query;
// Build filter
const filter = {};
if (status) filter.status = status;
if (author) filter.author = author;
if (search) filter.title = { $regex: search, $options: 'i' };
// Build sort (- prefix = descending)
let sortObj = {};
if (sort) {
sort.split(',').forEach(field => {
if (field.startsWith('-')) {
sortObj[field.slice(1)] = -1;
} else {
sortObj[field] = 1;
}
});
}
const posts = await Post.find(filter).sort(sortObj);
res.json(posts);
});API Versioning Questions
These questions test your understanding of API evolution and backward compatibility.
What are the different approaches to API versioning?
There are three main approaches to API versioning, each with trade-offs:
- URL path versioning (
/api/v1/users) - Most common, clear, but changes URLs - Query parameter (
/api/users?version=1) - Flexible but easy to miss - Header versioning (
Accept: application/vnd.api.v1+json) - Cleanest URLs but harder to test
Best practices: version from the start, maintain backward compatibility within versions, deprecate old versions gradually with clear timelines, document breaking changes.
How do you implement URL path versioning?
URL path versioning is the most common approach. Mount different routers for each version:
// /api/v1/users
// /api/v2/users
const v1Router = require('./routes/v1');
const v2Router = require('./routes/v2');
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);How do you implement header versioning?
Header versioning keeps URLs clean but is harder to test in browsers:
// Accept: application/vnd.myapi.v1+json
app.use('/api', (req, res, next) => {
const accept = req.headers.accept || '';
const match = accept.match(/application\/vnd\.myapi\.v(\d+)\+json/);
req.apiVersion = match ? parseInt(match[1]) : 1;
next();
});
app.get('/api/users', (req, res) => {
if (req.apiVersion === 2) {
// V2 response format
return res.json({ data: users, meta: { count: users.length } });
}
// V1 response format
res.json(users);
});Error Handling Questions
These questions test your approach to API error responses and consistency.
How do you handle errors in a REST API?
Consistent error responses should include: appropriate status code, error message, error code for programmatic handling, and details when helpful. Use a standard format across your entire API:
// Error response format
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{ "field": "email", "message": "Invalid email format" }
]
}
}
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err);
if (err.name === 'ValidationError') {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: err.message,
details: Object.values(err.errors).map(e => ({
field: e.path,
message: e.message
}))
}
});
}
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'An unexpected error occurred'
}
});
});What is the difference between REST and GraphQL?
REST uses multiple endpoints with fixed data structures—you might need several requests to get related data. GraphQL uses a single endpoint where clients specify exactly what data they need in one request.
REST is simpler, better cached, and more established. GraphQL is more flexible, eliminates over-fetching, and is great for complex, nested data. Choose REST for simpler APIs and GraphQL when clients have diverse data needs.
API Security Questions
These questions test your understanding of securing APIs in production.
How do you secure a REST API?
API security requires multiple layers working together:
- HTTPS - Always, no exceptions
- Authentication - JWT or OAuth for user APIs, API keys for service-to-service
- Authorization - Check permissions on every request
- Rate limiting - Prevent abuse
- Input validation - Never trust client data
- CORS - Restrict allowed origins
- Security headers - Helmet.js for Node
How do you prevent duplicate resources from POST timeouts?
Use idempotency keys—the client sends a unique ID with the request, the server stores it and returns the same response for duplicate IDs. Alternatively, use PUT with client-generated IDs instead of POST.
Quick Reference
| Method | Purpose | Idempotent | Request Body |
|---|---|---|---|
| GET | Retrieve | Yes | No |
| POST | Create | No | Yes |
| PUT | Replace | Yes | Yes |
| PATCH | Update | Maybe | Yes |
| DELETE | Remove | Yes | Optional |
| Status | Meaning | Use Case |
|---|---|---|
| 200 | OK | Successful GET/PUT/PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid syntax |
| 401 | Unauthorized | Auth required |
| 403 | Forbidden | No permission |
| 404 | Not Found | Resource missing |
| 422 | Unprocessable | Validation failed |
| 500 | Server Error | Something broke |
Related Articles
- Complete Node.js Backend Developer Interview Guide - comprehensive preparation guide for backend interviews
- Express.js Middleware Interview Guide - Middleware patterns and request lifecycle
- Node.js Advanced Interview Guide - Event loop, streams, and Node.js internals
- System Design Interview Guide - Scalability, reliability, and distributed systems
