Understanding REST Principles
Resource-Based URL Design
Nouns, Not Verbs
URL Design
# Good: Resource-based URLs
GET /users
GET /users/123
POST /users
PUT /users/123
DELETE /users/123
# Bad: Action-based URLs
GET /getUsers
POST /createUser
PUT /updateUser/123
DELETE /deleteUser/123Hierarchical Resources
Hierarchical Resources
# Good: Hierarchical structure
GET /users/123/orders
GET /users/123/orders/456
POST /users/123/orders
PUT /users/123/orders/456
DELETE /users/123/orders/456
# For sub-resources that don't belong to a parent
GET /orders/456/items
POST /orders/456/itemsHTTP Methods and Status Codes
Proper HTTP Method Usage
HTTP Methods
// GET - Retrieve resources
app.get('/users', async (req, res) => {
const users = await userService.getAllUsers();
res.json(users);
});
// POST - Create new resources
app.post('/users', async (req, res) => {
try {
const user = await userService.createUser(req.body);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// PUT - Update entire resource
app.put('/users/:id', async (req, res) => {
try {
const user = await userService.updateUser(req.params.id, req.body);
res.json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});Meaningful Status Codes
Status Codes
// Success responses
200 OK // Successful GET, PUT, PATCH
201 Created // Successful POST
204 No Content // Successful DELETE
// Client error responses
400 Bad Request // Invalid request data
401 Unauthorized // Authentication required
403 Forbidden // Insufficient permissions
404 Not Found // Resource doesn't exist
409 Conflict // Resource conflict
422 Unprocessable Entity // Validation errors
// Server error responses
500 Internal Server Error // Server error
502 Bad Gateway // Upstream server error
503 Service Unavailable // Service temporarily unavailableRequest and Response Design
Consistent Response Format
Response Format
// Standard response wrapper
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: {
message: string;
code: string;
details?: any;
};
meta?: {
pagination?: {
page: number;
limit: number;
total: number;
totalPages: number;
};
timestamp: string;
};
}
// Success response helper
function sendSuccess<T>(res: Response, data: T, statusCode = 200) {
const response: ApiResponse<T> = {
success: true,
data,
meta: {
timestamp: new Date().toISOString()
}
};
res.status(statusCode).json(response);
}Pagination
Pagination
app.get('/users', async (req, res) => {
try {
const { page = 1, limit = 10, sort = 'id', order = 'asc' } = req.query;
const paginationParams = {
page: parseInt(page as string),
limit: parseInt(limit as string),
sort: sort as string,
order: order as 'asc' | 'desc'
};
const result = await userService.getUsersPaginated(paginationParams);
const response = {
data: result.users,
pagination: {
page: paginationParams.page,
limit: paginationParams.limit,
total: result.total,
totalPages: Math.ceil(result.total / paginationParams.limit),
hasNext: paginationParams.page < Math.ceil(result.total / paginationParams.limit),
hasPrev: paginationParams.page > 1
}
};
sendSuccess(res, response);
} catch (error) {
sendError(res, 'Failed to fetch users', 'FETCH_ERROR', 500);
}
});Error Handling
Consistent Error Format
Error Handling
// Error types
enum ErrorCode {
VALIDATION_ERROR = 'VALIDATION_ERROR',
NOT_FOUND = 'NOT_FOUND',
UNAUTHORIZED = 'UNAUTHORIZED',
FORBIDDEN = 'FORBIDDEN',
CONFLICT = 'CONFLICT',
INTERNAL_ERROR = 'INTERNAL_ERROR'
}
// Custom error class
class ApiError extends Error {
constructor(
public message: string,
public code: ErrorCode,
public statusCode: number = 400,
public details?: any
) {
super(message);
this.name = 'ApiError';
}
}Security Best Practices
Authentication and Authorization
Authentication
// JWT authentication middleware
function authenticateToken(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET!, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
req.user = user as any;
next();
});
}Best Practices
- Use resource-based URLs - Make your API intuitive
- Leverage HTTP methods and status codes - Use the protocol correctly
- Maintain consistent response formats - Make responses predictable
- Implement proper error handling - Help developers debug issues
- Version your API - Plan for evolution
- Secure your endpoints - Protect your data and users
- Document everything - Make your API discoverable
- Test thoroughly - Ensure reliability