🎯 Learning Objectives
After completing this course, you will be able to:
- Understand REST architectural principles
- Design RESTful API endpoints
- Implement proper HTTP methods and status codes
- Handle API versioning
- Implement pagination and filtering
- Document APIs effectively
- Test and debug APIs
⚡ Prerequisites
- Backend Development Course completed
- Understanding of HTTP protocol
- JSON data format knowledge
- Basic database concepts
1. REST Principles
What is REST?
REST (Representational State Transfer) is an architectural style for designing networked applications. It relies on stateless, client-server communication using HTTP.
REST Constraints
- Client-Server: Separation of concerns
- Stateless: Each request contains all needed information
- Cacheable: Responses must define cacheability
- Uniform Interface: Consistent API design
- Layered System: Client can't tell if connected directly
- Code on Demand: Optional - server can send executable code
REST Benefits
- Scalability
- Simplicity
- Platform independence
- Language independence
- Easy to understand and use
- Cacheable responses
2. HTTP Methods
CRUD Operations with HTTP Methods
GET /api/users
// Retrieve all users
// Response: 200 OK with array of users
GET /api/users/:id
// Retrieve a specific user
// Response: 200 OK with user object or 404 Not Found
POST /api/users
// Create a new user
// Body: { "name": "John", "email": "john@example.com" }
// Response: 201 Created with new user object
PUT /api/users/:id
// Update entire user (replace)
// Body: { "name": "John Doe", "email": "john@example.com" }
// Response: 200 OK with updated user or 404 Not Found
PATCH /api/users/:id
// Partially update user
// Body: { "name": "John Doe" }
// Response: 200 OK with updated user or 404 Not Found
DELETE /api/users/:id
// Delete a user
// Response: 204 No Content or 404 Not Found
Implementation Example
const express = require('express');
const router = express.Router();
// GET all users
router.get('/users', async (req, res) => {
try {
const users = await User.find();
res.json({
success: true,
count: users.length,
data: users
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// GET single user
router.get('/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: user
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// POST create user
router.post('/users', async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json({
success: true,
data: user
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message
});
}
});
// PUT update user (full replacement)
router.put('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true, overwrite: true }
);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: user
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message
});
}
});
// PATCH update user (partial update)
router.patch('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: user
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message
});
}
});
// DELETE user
router.delete('/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.status(204).send();
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
module.exports = router;
3. HTTP Status Codes
2xx Success
- 200 OK: Request succeeded
- 201 Created: Resource created
- 204 No Content: Success, no response body
4xx Client Errors
- 400 Bad Request: Invalid request
- 401 Unauthorized: Authentication required
- 403 Forbidden: No permission
- 404 Not Found: Resource doesn't exist
- 422 Unprocessable Entity: Validation error
5xx Server Errors
- 500 Internal Server Error: Server error
- 502 Bad Gateway: Invalid response from upstream
- 503 Service Unavailable: Server temporarily unavailable
4. Endpoint Design
RESTful URL Structure
// Resource Collections
GET /api/users // List all users
POST /api/users // Create new user
// Single Resources
GET /api/users/:id // Get specific user
PUT /api/users/:id // Update user (full)
PATCH /api/users/:id // Update user (partial)
DELETE /api/users/:id // Delete user
// Nested Resources
GET /api/users/:id/posts // Get user's posts
POST /api/users/:id/posts // Create post for user
GET /api/users/:id/posts/:postId // Get specific post
// Actions (when CRUD doesn't fit)
POST /api/users/:id/activate // Activate user
POST /api/users/:id/deactivate // Deactivate user
POST /api/posts/:id/publish // Publish post
// Filtering and Searching
GET /api/users?role=admin // Filter by role
GET /api/users?search=john // Search users
GET /api/posts?status=published // Filter posts
// Sorting
GET /api/users?sort=name // Sort by name ascending
GET /api/users?sort=-createdAt // Sort by date descending
// Field Selection
GET /api/users?fields=name,email // Return only specific fields
⚠️ Endpoint Design Best Practices
- Use nouns, not verbs (❌ /getUsers, ✅ /users)
- Use plural nouns for collections (✅ /users, not /user)
- Use lowercase and hyphens (✅ /user-profiles, not /userProfiles)
- Keep URLs simple and intuitive
- Avoid deep nesting (max 2-3 levels)
- Use query parameters for filtering, not URL paths
- Version your API (/api/v1/users)
Pagination Implementation
// Pagination with page and limit
router.get('/users', async (req, res) => {
try {
// Parse query parameters
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
// Get total count
const total = await User.countDocuments();
// Get paginated results
const users = await User.find()
.limit(limit)
.skip(skip)
.sort({ createdAt: -1 });
// Calculate pagination info
const totalPages = Math.ceil(total / limit);
const hasNextPage = page < totalPages;
const hasPrevPage = page > 1;
res.json({
success: true,
data: users,
pagination: {
total,
page,
limit,
totalPages,
hasNextPage,
hasPrevPage,
nextPage: hasNextPage ? page + 1 : null,
prevPage: hasPrevPage ? page - 1 : null
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// Cursor-based pagination (for large datasets)
router.get('/posts', async (req, res) => {
try {
const limit = parseInt(req.query.limit) || 20;
const cursor = req.query.cursor;
let query = {};
if (cursor) {
query._id = { $lt: cursor };
}
const posts = await Post.find(query)
.limit(limit + 1)
.sort({ _id: -1 });
const hasMore = posts.length > limit;
const results = hasMore ? posts.slice(0, -1) : posts;
const nextCursor = hasMore ? results[results.length - 1]._id : null;
res.json({
success: true,
data: results,
pagination: {
nextCursor,
hasMore
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
Filtering and Sorting
router.get('/users', async (req, res) => {
try {
// Build query object
const queryObj = { ...req.query };
const excludedFields = ['page', 'sort', 'limit', 'fields'];
excludedFields.forEach(field => delete queryObj[field]);
// Advanced filtering (gte, gt, lte, lt)
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g, match => `$${match}`);
let query = User.find(JSON.parse(queryStr));
// Sorting
if (req.query.sort) {
const sortBy = req.query.sort.split(',').join(' ');
query = query.sort(sortBy);
} else {
query = query.sort('-createdAt');
}
// Field limiting
if (req.query.fields) {
const fields = req.query.fields.split(',').join(' ');
query = query.select(fields);
}
// Pagination
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
query = query.skip(skip).limit(limit);
// Execute query
const users = await query;
const total = await User.countDocuments(JSON.parse(queryStr));
res.json({
success: true,
count: users.length,
total,
data: users
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// Example requests:
// GET /api/users?age[gte]=18&age[lte]=65
// GET /api/users?sort=-createdAt,name
// GET /api/users?fields=name,email
// GET /api/users?page=2&limit=20
6. API Versioning
Versioning Strategies
// 1. URL Versioning (Most Common)
GET /api/v1/users
GET /api/v2/users
// Implementation
const v1Routes = require('./routes/v1');
const v2Routes = require('./routes/v2');
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);
// 2. Header Versioning
GET /api/users
Headers: {
'Accept-Version': 'v1'
}
// Implementation
app.use((req, res, next) => {
const version = req.headers['accept-version'] || 'v1';
req.apiVersion = version;
next();
});
// 3. Query Parameter Versioning
GET /api/users?version=1
// 4. Content Negotiation
GET /api/users
Headers: {
'Accept': 'application/vnd.myapi.v1+json'
}
7. API Documentation
OpenAPI/Swagger Documentation
// Install: npm install swagger-jsdoc swagger-ui-express
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'User API',
version: '1.0.0',
description: 'A simple User API'
},
servers: [
{
url: 'http://localhost:3000',
description: 'Development server'
}
]
},
apis: ['./routes/*.js']
};
const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
// In routes file:
/**
* @swagger
* /api/users:
* get:
* summary: Retrieve a list of users
* description: Retrieve a list of users from the database
* responses:
* 200:
* description: A list of users
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* properties:
* id:
* type: string
* name:
* type: string
* email:
* type: string
*/
router.get('/users', async (req, res) => {
// Implementation
});
/**
* @swagger
* /api/users:
* post:
* summary: Create a new user
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* properties:
* name:
* type: string
* email:
* type: string
* responses:
* 201:
* description: User created successfully
*/
router.post('/users', async (req, res) => {
// Implementation
});
Practice Exercises
Build a RESTful API for an e-commerce platform:
- Products CRUD with categories
- Shopping cart management
- Order creation and tracking
- Pagination, filtering, and sorting
- Proper status codes and error handling
Document your API using Swagger/OpenAPI:
- Set up Swagger UI
- Document all endpoints
- Include request/response examples
- Add authentication documentation
Implement API versioning:
- Create v1 and v2 of your API
- Add breaking changes in v2
- Maintain backward compatibility in v1
- Document version differences
🎉 Summary
You've completed the REST APIs course! You now know:
- REST architectural principles and constraints
- Proper use of HTTP methods and status codes
- RESTful endpoint design patterns
- Pagination and filtering implementation
- API versioning strategies
- API documentation with Swagger/OpenAPI
- Best practices for API development
Total points available: 10/10