🔌 REST APIs Course

🎯 Learning Objectives

After completing this course, you will be able to:

⚡ 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

5. Pagination & Filtering

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

📝 Exercise 1: E-commerce API (4 points)

Build a RESTful API for an e-commerce platform:

📝 Exercise 2: API Documentation (3 points)

Document your API using Swagger/OpenAPI:

📝 Exercise 3: API Versioning (3 points)

Implement API versioning:

🎉 Summary

You've completed the REST APIs course! You now know:

Total points available: 10/10

➡️ Next

Course 8: GraphQL