Skip to content

rajsriv/RestAPI-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 

Repository files navigation

RestAPI-guide

Table of Contents

  1. REST API Basics
  2. HTTP Methods & Status Codes
  3. API Design Principles
  4. Implementation Examples
  5. Authentication & Security
  6. Testing & Documentation
  7. Best Practices
  8. Tools & Resources

REST API Basics

What is REST? REST (Representational State Transfer) is an architectural style for designing networked applications. It uses standard HTTP methods and is stateless, meaning each request contains all information needed to process it.

Core REST Principles ⦁ Stateless: Each request is independent ⦁ Client-Server: Separation of concerns ⦁ Cacheable: Responses can be cached ⦁ Uniform Interface: Consistent API structure ⦁ Layered System: Architecture can have multiple layers

HTTP Methods & Status Codes

HTTP Methods

Method Purpose Example
GET Retrieve data GET /api/users
POST Create new resource POST /api/users
PUT Update entire resource PUT /api/users/123
PATCH Partial update PATCH /api/users/123
DELETE Remove resource DELETE /api/users/123

Common Status Codes

Code Meaning When to Use
200 OK Successful GET, PUT, PATCH
201 Created Successful POST
204 No Content Successful DELETE
400 Bad Request Invalid request data
401 Unauthorized Authentication required
403 Forbidden Access denied
404 Not Found Resource doesn't exist
500 Internal Server Error Server-side error

API Design Principles

  1. URL Structure
// Good URLs
GET /api/v1/users                    // Get all users
GET /api/v1/users/123               // Get specific user
GET /api/v1/users/123/posts         // Get user's posts
POST /api/v1/users/123/posts        // Create new post for user

// Bad URLs
GET /api/v1/getUsers                // Don't use verbs
GET /api/v1/user/123/posts/get      // Redundant verb
  1. Consistent Naming ⦁ Use nouns for resources, not verbs ⦁ Use plural forms (/users, not /user) ⦁ Use kebab-case for multi-word resources (/user-profiles) ⦁ Be consistent with naming conventions
  2. Query Parameters
GET /api/v1/users?page=2&limit=10&sort=name&order=asc&filter=active

Implementation Examples

Node.js + Express

const express = require('express');
const app = express();

app.use(express.json());

// In-memory data (use database in production)
let users = [
  { id: 1, name: 'John Doe', email: 'john@example.com' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];

// GET all users
app.get('/api/users', (req, res) => {
  res.status(200).json({
    success: true,
    data: users,
    count: users.length
  });
});

// GET single user
app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  
  if (!user) {
    return res.status(404).json({
      success: false,
      message: 'User not found'
    });
  }
  
  res.status(200).json({
    success: true,
    data: user
  });
});

// CREATE user
app.post('/api/users', (req, res) => {
  const { name, email } = req.body;
  
  // Validation
  if (!name || !email) {
    return res.status(400).json({
      success: false,
      message: 'Name and email are required'
    });
  }
  
  const newUser = {
    id: users.length + 1,
    name,
    email
  };
  
  users.push(newUser);
  
  res.status(201).json({
    success: true,
    data: newUser
  });
});

// UPDATE user
app.put('/api/users/:id', (req, res) => {
  const userId = parseInt(req.params.id);
  const userIndex = users.findIndex(u => u.id === userId);
  
  if (userIndex === -1) {
    return res.status(404).json({
      success: false,
      message: 'User not found'
    });
  }
  
  const { name, email } = req.body;
  users[userIndex] = { id: userId, name, email };
  
  res.status(200).json({
    success: true,
    data: users[userIndex]
  });
});

// DELETE user
app.delete('/api/users/:id', (req, res) => {
  const userId = parseInt(req.params.id);
  const userIndex = users.findIndex(u => u.id === userId);
  
  if (userIndex === -1) {
    return res.status(404).json({
      success: false,
      message: 'User not found'
    });
  }
  
  users.splice(userIndex, 1);
  
  res.status(204).send();
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Python + Flask

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(100), nullable=False, unique=True)
    
    def to_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'email': self.email
        }

@app.route('/api/users', methods=['GET'])
def get_users():
    users = User.query.all()
    return jsonify({
        'success': True,
        'data': [user.to_dict() for user in users],
        'count': len(users)
    })

@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = User.query.get(user_id)
    if not user:
        return jsonify({'success': False, 'message': 'User not found'}), 404
    
    return jsonify({'success': True, 'data': user.to_dict()})

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    
    if not data.get('name') or not data.get('email'):
        return jsonify({
            'success': False, 
            'message': 'Name and email are required'
        }), 400
    
    user = User(name=data['name'], email=data['email'])
    db.session.add(user)
    db.session.commit()
    
    return jsonify({'success': True, 'data': user.to_dict()}), 201

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

Java + Spring Boot

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public ResponseEntity<ApiResponse<List<User>>> getAllUsers() {
        List<User> users = userService.findAll();
        return ResponseEntity.ok(new ApiResponse<>(true, users, users.size()));
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<ApiResponse<User>> getUserById(@PathVariable Long id) {
        Optional<User> user = userService.findById(id);
        
        if (user.isEmpty()) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(new ApiResponse<>(false, "User not found"));
        }
        
        return ResponseEntity.ok(new ApiResponse<>(true, user.get()));
    }
    
    @PostMapping
    public ResponseEntity<ApiResponse<User>> createUser(@RequestBody @Valid User user) {
        User savedUser = userService.save(user);
        return ResponseEntity.status(HttpStatus.CREATED)
            .body(new ApiResponse<>(true, savedUser));
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<ApiResponse<User>> updateUser(
            @PathVariable Long id, 
            @RequestBody @Valid User user) {
        
        if (!userService.existsById(id)) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(new ApiResponse<>(false, "User not found"));
        }
        
        user.setId(id);
        User updatedUser = userService.save(user);
        return ResponseEntity.ok(new ApiResponse<>(true, updatedUser));
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        if (!userService.existsById(id)) {
            return ResponseEntity.notFound().build();
        }
        
        userService.deleteById(id);
        return ResponseEntity.noContent().build();
    }
}

Authentication & Security

  1. JWT Authentication
const jwt = require('jsonwebtoken');

// Middleware for JWT verification
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ message: 'Access token required' });
  }
  
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ message: 'Invalid token' });
    }
    req.user = user;
    next();
  });
};

// Login endpoint
app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body;
  
  // Verify user credentials (implement your logic)
  const user = await verifyUserCredentials(email, password);
  
  if (!user) {
    return res.status(401).json({ message: 'Invalid credentials' });
  }
  
  const token = jwt.sign(
    { userId: user.id, email: user.email },
    process.env.JWT_SECRET,
    { expiresIn: '24h' }
  );
  
  res.json({ token, user: { id: user.id, email: user.email } });
});

// Protected route
app.get('/api/protected', authenticateToken, (req, res) => {
  res.json({ message: 'This is protected', user: req.user });
});
  1. API Key Authentication
const authenticateApiKey = (req, res, next) => {
  const apiKey = req.headers['x-api-key'];
  
  if (!apiKey || !isValidApiKey(apiKey)) {
    return res.status(401).json({ message: 'Valid API key required' });
  }
  
  next();
};

app.use('/api', authenticateApiKey);
  1. Security Best Practices ⦁ Use HTTPS in production ⦁ Implement rate limiting ⦁ Validate and sanitize input ⦁ Use parameterized queries (prevent SQL injection) ⦁ Implement CORS properly ⦁ Log security events

Testing & Documentation

Testing with Jest (Node.js)

const request = require('supertest');
const app = require('./app');

describe('Users API', () => {
  test('GET /api/users should return all users', async () => {
    const response = await request(app)
      .get('/api/users')
      .expect(200);
    
    expect(response.body.success).toBe(true);
    expect(Array.isArray(response.body.data)).toBe(true);
  });
  
  test('POST /api/users should create new user', async () => {
    const newUser = {
      name: 'Test User',
      email: 'test@example.com'
    };
    
    const response = await request(app)
      .post('/api/users')
      .send(newUser)
      .expect(201);
    
    expect(response.body.success).toBe(true);
    expect(response.body.data.name).toBe(newUser.name);
  });
});

API Documentation (OpenAPI/Swagger)

openapi: 3.0.0
info:
  title: User Management API
  version: 1.0.0
  description: REST API for managing users

paths:
  /api/users:
    get:
      summary: Get all users
      responses:
        '200':
          description: List of users
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  count:
                    type: integer
    
    post:
      summary: Create new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserInput'
      responses:
        '201':
          description: User created successfully
        '400':
          description: Invalid input

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
    
    UserInput:
      type: object
      required:
        - name
        - email
      properties:
        name:
          type: string
        email:
          type: string

Best Practices

  1. Error Handling
// Consistent error response format
const errorHandler = (err, req, res, next) => {
  console.error(err.stack);
  
  const error = {
    success: false,
    message: err.message || 'Internal Server Error',
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  };
  
  const statusCode = err.statusCode || 500;
  res.status(statusCode).json(error);
};

app.use(errorHandler);
  1. Validation Middleware
const { body, validationResult } = require('express-validator');

const validateUser = [
  body('name').trim().isLength({ min: 1 }).withMessage('Name is required'),
  body('email').isEmail().withMessage('Valid email is required'),
  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        message: 'Validation failed',
        errors: errors.array()
      });
    }
    next();
  }
];

app.post('/api/users', validateUser, createUser);
  1. Pagination
app.get('/api/users', (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const offset = (page - 1) * limit;
  
  const users = getUsersPaginated(offset, limit);
  const total = getTotalUsers();
  
  res.json({
    success: true,
    data: users,
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit)
    }
  });
});
  1. Logging
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// Log API requests
app.use((req, res, next) => {
  logger.info(`${req.method} ${req.path} - ${req.ip}`);
  next();
});

Tools & Resources

Development Tools ⦁ Postman - API testing and documentation ⦁ Insomnia - REST client ⦁ Thunder Client - VS Code extension ⦁ Swagger/OpenAPI - API documentation ⦁ Newman - Command-line Postman

Testing Tools ⦁ Jest - JavaScript testing ⦁ Mocha/Chai - Node.js testing ⦁ pytest - Python testing ⦁ JUnit - Java testing

Database Integration

// MongoDB with Mongoose
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  createdAt: { type: Date, default: Date.now }
});

const User = mongoose.model('User', userSchema);

// PostgreSQL with pg
const { Pool } = require('pg');
const pool = new Pool({
  user: 'username',
  host: 'localhost',
  database: 'mydb',
  password: 'password',
  port: 5432,
});

Rate Limiting

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP'
});

app.use('/api/', limiter);

CORS Configuration

const cors = require('cors');

const corsOptions = {
  origin: ['http://localhost:3000', 'https://yourapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
};

app.use(cors(corsOptions));

Advanced Topics

  1. Caching with Redis
const redis = require('redis');
const client = redis.createClient();

const cacheMiddleware = (duration = 300) => {
  return async (req, res, next) => {
    const key = req.originalUrl;
    const cached = await client.get(key);
    
    if (cached) {
      return res.json(JSON.parse(cached));
    }
    
    res.sendResponse = res.json;
    res.json = (data) => {
      client.setex(key, duration, JSON.stringify(data));
      res.sendResponse(data);
    };
    
    next();
  };
};

app.get('/api/users', cacheMiddleware(600), getUsers);
  1. API Versioning
// URL versioning
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);

// Header versioning
app.use((req, res, next) => {
  const version = req.headers['api-version'] || 'v1';
  req.apiVersion = version;
  next();
});
  1. File Upload
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

app.post('/api/users/:id/avatar', upload.single('avatar'), (req, res) => {
  // Handle file upload
  res.json({
    success: true,
    message: 'Avatar uploaded successfully',
    filename: req.file.filename
  });
});

This comprehensive guide covers everything from basic REST principles to advanced implementation patterns. Start with the basics and gradually implement more advanced features as your API grows!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors