RESTful API Best Practices

1. Use Nouns for Resources

// ✅ Good
GET    /api/users
POST   /api/users
GET    /api/users/123
PUT    /api/users/123
DELETE /api/users/123

// ❌ Bad
GET    /api/getUsers
POST   /api/createUser
GET    /api/getUserById/123

2. Use Plural Names

// ✅ Good
/api/users
/api/products
/api/orders

// ❌ Bad
/api/user
/api/product
/api/order

3. Use HTTP Methods Correctly

// CRUD operations
GET    /api/users      // Read all
GET    /api/users/123  // Read one
POST   /api/users      // Create
PUT    /api/users/123  // Update (replace)
PATCH  /api/users/123  // Update (partial)
DELETE /api/users/123  // Delete

4. Use Proper Status Codes

// Success
200 OK              // Successful GET, PUT, PATCH
201 Created         // Successful POST
204 No Content      // Successful DELETE

// Client Errors
400 Bad Request     // Invalid input
401 Unauthorized    // Not authenticated
403 Forbidden       // Not authorized
404 Not Found       // Resource doesn't exist
409 Conflict        // Resource conflict
422 Unprocessable   // Validation error

// Server Errors
500 Internal Error  // Server error
503 Unavailable     // Service down

5. Version Your API

// URL versioning (recommended)
/api/v1/users
/api/v2/users

// Header versioning
GET /api/users
Accept: application/vnd.myapi.v2+json

// Query parameter
/api/users?version=2

6. Use Filtering, Sorting, Pagination

// Filtering
GET /api/users?role=admin&status=active

// Sorting
GET /api/users?sort=-createdAt

// Pagination
GET /api/users?page=2&limit=20

// Combined
GET /api/users?role=admin&sort=-createdAt&page=1&limit=10

7. Provide Consistent Error Responses

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": [
      {
        "field": "email",
        "message": "Email is required"
      }
    ],
    "timestamp": "2024-01-01T00:00:00Z",
    "path": "/api/users"
  }
}

8. Use HTTPS

// Always use HTTPS in production
https://api.example.com/users

// Redirect HTTP to HTTPS
app.use((req, res, next) => {
  if (!req.secure && process.env.NODE_ENV === 'production') {
    return res.redirect(`https://${req.headers.host}${req.url}`);
  }
  next();
});

9. Implement Rate Limiting

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

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: 'Too many requests'
});

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

10. Use Authentication & Authorization

// JWT authentication
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  if (!token) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

// Role-based authorization
const authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
};

app.delete('/api/users/:id', authenticate, authorize('admin'), deleteUser);

11. Document Your API

/**
 * @swagger
 * /api/users:
 *   get:
 *     summary: Get all users
 *     tags: [Users]
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *     responses:
 *       200:
 *         description: List of users
 */
app.get('/api/users', getUsers);

12. Use Caching

// Cache-Control headers
app.get('/api/users/:id', async (req, res) => {
  const user = await User.findById(req.params.id);
  
  res.set('Cache-Control', 'public, max-age=300');
  res.set('ETag', generateETag(user));
  
  res.json(user);
});

// Check ETag
if (req.headers['if-none-match'] === etag) {
  return res.status(304).send();
}

13. Handle CORS Properly

const cors = require('cors');

app.use(cors({
  origin: process.env.ALLOWED_ORIGINS.split(','),
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

14. Validate Input

const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().min(2).max(100).required(),
  email: Joi.string().email().required(),
  age: Joi.number().min(0).max(150)
});

app.post('/api/users', async (req, res) => {
  const { error } = userSchema.validate(req.body);
  
  if (error) {
    return res.status(422).json({
      error: 'Validation failed',
      details: error.details
    });
  }
  
  const user = await User.create(req.body);
  res.status(201).json(user);
});

15. Use Compression

const compression = require('compression');

app.use(compression());

16. Implement Logging

const winston = require('winston');

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

app.use((req, res, next) => {
  logger.info({
    method: req.method,
    path: req.path,
    ip: req.ip
  });
  next();
});

17. Use Idempotency Keys for Critical Operations

app.post('/api/payments', async (req, res) => {
  const idempotencyKey = req.headers['idempotency-key'];
  
  if (!idempotencyKey) {
    return res.status(400).json({ error: 'Idempotency-Key required' });
  }
  
  const existing = await Payment.findOne({ idempotencyKey });
  if (existing) {
    return res.status(201).json(existing);
  }
  
  const payment = await Payment.create({
    idempotencyKey,
    ...req.body
  });
  
  res.status(201).json(payment);
});

18. Return Appropriate Response Bodies

// POST - Return created resource
app.post('/api/users', async (req, res) => {
  const user = await User.create(req.body);
  res.status(201).json(user);
});

// PUT - Return updated resource
app.put('/api/users/:id', async (req, res) => {
  const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
  res.json(user);
});

// DELETE - Return 204 No Content
app.delete('/api/users/:id', async (req, res) => {
  await User.findByIdAndDelete(req.params.id);
  res.status(204).send();
});

19. Use Nested Resources Wisely

// ✅ Good - Clear relationship
GET /api/users/123/orders

// ❌ Bad - Too deep
GET /api/users/123/orders/456/items/789/reviews

// Better - Flat structure
GET /api/reviews?itemId=789

20. Implement Health Checks

app.get('/health', async (req, res) => {
  try {
    await db.ping();
    res.status(200).json({
      status: 'healthy',
      timestamp: new Date().toISOString()
    });
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message
    });
  }
});

Interview Tips

  • Explain principles: RESTful design principles
  • Show examples: Good vs bad practices
  • Demonstrate security: Auth, HTTPS, validation
  • Discuss performance: Caching, compression
  • Mention documentation: Swagger/OpenAPI
  • Show error handling: Consistent error responses

Summary

RESTful API best practices include using nouns for resources, proper HTTP methods and status codes, API versioning, filtering/sorting/pagination, consistent error responses, HTTPS, rate limiting, authentication/authorization, documentation, caching, CORS, input validation, compression, logging, idempotency, appropriate response bodies, wise use of nested resources, and health checks. Essential for production-ready REST APIs.

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your Restful-api Knowledge

Ready to put your skills to the test? Take our interactive Restful-api quiz and get instant feedback on your answers.