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. Here's how to nail REST API questions.
The 30-Second Answer
When the interviewer asks "What is REST?", here's your concise answer:
"REST is an architectural style for APIs that uses standard HTTP methods on resources identified by URLs. GET retrieves data, POST creates, PUT replaces, PATCH updates, DELETE removes. It's stateless - each request contains all needed information. The key principles are: use nouns for URLs, HTTP methods for actions, proper status codes for responses, and consistent resource representations."
Wait for follow-up questions.
The 2-Minute Answer (If They Want More)
If they ask you to elaborate:
"REST stands for Representational State Transfer. 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 userProper status codes tell clients what happened: 200s for success, 400s for client errors, 500s for server errors."
HTTP Methods Explained
The Big Five
GET - Retrieve resource(s) - Safe, Idempotent, Cacheable
POST - Create new resource - NOT Safe, NOT Idempotent
PUT - Replace entire resource - NOT Safe, Idempotent
PATCH - Partial update - NOT Safe, May be Idempotent
DELETE - Remove resource - NOT Safe, Idempotent
Code Examples
// 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
});The Classic Problem: URL Design
This question tests REST understanding:
Interviewer: "Design the endpoints for a blog API with users, posts, and comments."
The Bug (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
The Solution (RESTful Design)
# 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: Know These Cold
Success Codes (2xx)
// 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();Client Error Codes (4xx)
// 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
});Server Error Codes (5xx)
// 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 to Use What
| 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 Methods
API Keys
// Simple but less secure - good for server-to-server
// Pass in header
const response = await fetch('/api/data', {
headers: {
'X-API-Key': 'your-api-key-here'
}
});
// Server validation
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();
});JWT (JSON Web Tokens)
// 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' });
}
}OAuth 2.0
// 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, Filtering, and Sorting
Pagination
// 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
}
});
});Filtering and Sorting
// 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
URL Path Versioning (Most Common)
// /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);Header Versioning
// 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);
});Common Follow-Up Questions
"What's 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. I choose REST for simpler APIs and GraphQL when clients have diverse data needs."
"How do you handle errors in a REST API?"
"Consistent error responses with: appropriate status code, error message, error code for programmatic handling, and details when helpful.
I use a standard format:"
// 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'
}
});
});"How do you secure a REST API?"
"Multiple layers:
- 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"
"What is HATEOAS?"
"Hypermedia As The Engine Of Application State - 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" }
}
}What Interviewers Are Really Testing
When I ask about REST APIs, I'm checking:
- HTTP fundamentals - Methods, status codes, headers
- URL design - Resource naming, hierarchy, consistency
- Best practices - Versioning, pagination, error handling
- Security awareness - Authentication, authorization, input validation
- Practical experience - Can you design an API for a real scenario?
A candidate who designs clean URLs, uses correct status codes, and mentions security and versioning will stand out.
Quick Reference Card
| 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 |
Practice Questions
Test yourself before your interview:
1. Design REST endpoints for an e-commerce cart system.
2. What's wrong with these endpoints?
POST /api/users/123/updateEmail
GET /api/getProductById?id=456
DELETE /api/products/delete/789
3. A client creates a resource with POST but gets a timeout. They retry and now have duplicate entries. How do you prevent this?
4. What status code would you return when a user tries to delete a resource that has dependent resources?
Answers:
GET /carts/:userId # Get user's cart
POST /carts/:userId/items # Add item to cart
PATCH /carts/:userId/items/:id # Update quantity
DELETE /carts/:userId/items/:id # Remove item
DELETE /carts/:userId # Clear cart
POST /carts/:userId/checkout # Convert to order
- Problems: verbs in URLs, inconsistent patterns. Fixed:
PATCH /api/users/123 # Body: { "email": "..." }
GET /api/products/456
DELETE /api/products/789
-
Use idempotency keys - client sends a unique ID with the request, server stores it and returns the same response for duplicate IDs. Or use PUT with client-generated IDs instead of POST.
-
409 Conflict - there's a conflict with the current state of the resource. Include a message explaining why (e.g., "Cannot delete category with existing products").
Related Articles
If you found this helpful, check out these related guides:
- 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
Ready for More Backend Interview Questions?
This is just one topic from our complete backend interview prep guide. Get access to 50+ questions covering:
- Database design and SQL
- System design fundamentals
- Microservices architecture
- Caching strategies
- Message queues and async processing
Get Full Access to All Backend Questions →
Or try our free Backend preview to see more questions like this.
Written by the EasyInterview team, based on real interview experience from 12+ years in tech and hundreds of technical interviews conducted at companies like BNY Mellon, UBS, and leading fintech firms.
