API Overview

FutureRent API is a comprehensive property management system built with Node.js, TypeScript, and Express. It provides endpoints for user management, property listings, rental agreements, and more.

Tech Stack

  • Node.js & Express
  • TypeScript
  • Winston Logger
  • JWT Authentication

Features

  • Comprehensive Logging
  • Role-based Access
  • RESTful API Design
  • Error Handling

Security

  • JWT Tokens
  • Input Validation
  • Secure Headers
  • Rate Limiting

Authentication

All protected endpoints require a valid JWT token in the Authorization header.

Authorization: Bearer <your_jwt_token>

Authentication Flow

  1. User sends login request with credentials
  2. Server validates credentials and returns JWT token
  3. Client includes token in Authorization header for subsequent requests
  4. Middleware validates token on each protected endpoint

API Endpoints

User Management

POST /api/v1/auth/login

Authenticate user and return JWT token.

{
  "email": "user@example.com",
  "password": "password123"
}
POST /api/v1/auth/register

Register a new user account.

{
  "name": "John Doe",
  "email": "john@example.com",
  "password": "securepassword",
  "role": "tenant"
}
GET /api/v1/users/profile

Get current user profile (requires authentication).

Property Management

GET /api/v1/properties

Get list of properties with pagination and filtering.

GET /api/v1/properties?page=1&limit=10&type=apartment
POST /api/v1/properties

Create a new property listing (requires landlord role).

{
  "title": "Modern Apartment",
  "description": "Spacious 2-bedroom apartment...",
  "price": 1500,
  "type": "apartment",
  "location": {
    "address": "123 Main St",
    "city": "New York",
    "state": "NY"
  }
}

Middleware

Authentication Middleware

Validates JWT tokens and attaches user data to requests.

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
    const token = req.header('Authorization')?.replace('Bearer ', '');
    
    if (!token) {
        return res.status(401).json({ error: 'Access denied. No token provided.' });
    }
    
    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET!);
        (req as any).user = decoded;
        next();
    } catch (error) {
        res.status(400).json({ error: 'Invalid token.' });
    }
};

Activity Logging Middleware

Logs all incoming requests and responses.

import { Request, Response, NextFunction } from 'express';
import ActivityLogger from '../utils/activityLogger';

export const activityLogMiddleware = (req: Request, res: Response, next: NextFunction) => {
    ActivityLogger.logRequest(req, res, next);
};

export const errorLogMiddleware = (error: any, req: Request, res: Response, next: NextFunction) => {
    ActivityLogger.logError(error, req, res);
    next(error);
};

Utility Functions

Activity Logger

Comprehensive logging utility for request/response tracking.

static logRequest(req: Request, res: Response, next: NextFunction): void {
    const startTime = Date.now();
    const logData = this.createLogData(req, res, startTime);

    // Check for authorization header
    const authHeader = req.headers['authorization'] || req.headers['Authorization'];
    if (!authHeader) {
        res.status(401).json({
            error: 'Access denied',
            message: 'Authorization header is required',
        });
        ActivityLogger.logError({
            message: 'Access denied - No authorization header provided',
            stack: "Auth Controller: Authorization header is required",
            name: "Auth Controller",
        }, req, res)
        return;
    }
    
    // Continue with request processing...
}

Response Helper

Standardized response format utility.

export class ResponseHelper {
    static success(res: Response, data: any, message: string = 'Success', statusCode: number = 200) {
        return res.status(statusCode).json({
            success: true,
            message,
            data
        });
    }
    
    static error(res: Response, message: string, statusCode: number = 500, errorDetails?: any) {
        return res.status(statusCode).json({
            success: false,
            message,
            error: errorDetails
        });
    }
}

Logging System

Log Files Structure

activity.log

Normal request/response logging for successful operations.

error.log

Error logging for failed requests and exceptions.

app_control.log

Authorization and application control events.

Log Entry Example

{
  "logId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2023-10-05T12:00:00.000Z",
  "level": "info",
  "message": "GET /api/v1/users/profile",
  "requestId": "req-123456",
  "userId": "user-789",
  "ipAddress": "192.168.1.100",
  "userAgent": "Mozilla/5.0...",
  "method": "GET",
  "url": "/api/v1/users/profile",
  "headers": { "authorization": "***REDACTED***" },
  "responseStatus": 200,
  "responseTime": 45
}

Error Handling

Custom Error Classes

export class AppError extends Error {
    public statusCode: number;
    public isOperational: boolean;

    constructor(message: string, statusCode: number = 500) {
        super(message);
        this.statusCode = statusCode;
        this.isOperational = true;
        
        Error.captureStackTrace(this, this.constructor);
    }
}

export class ValidationError extends AppError {
    constructor(message: string, details?: any) {
        super(message, 400);
        this.name = 'ValidationError';
    }
}

export class AuthenticationError extends AppError {
    constructor(message: string = 'Authentication failed') {
        super(message, 401);
        this.name = 'AuthenticationError';
    }
}

Global Error Handler

export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => {
    // Log error
    ActivityLogger.logError(err, req, res);
    
    // Default error
    let error = { ...err };
    error.message = err.message;

    // Mongoose bad ObjectId
    if (err.name === 'CastError') {
        const message = 'Resource not found';
        error = new AppError(message, 404);
    }

    // Mongoose duplicate key
    if (err.code === 11000) {
        const message = 'Duplicate field value entered';
        error = new AppError(message, 400);
    }

    // Mongoose validation error
    if (err.name === 'ValidationError') {
        const message = Object.values(err.errors).map((val: any) => val.message);
        error = new AppError(message.join(', '), 400);
    }

    res.status(error.statusCode || 500).json({
        success: false,
        message: error.message || 'Server Error'
    });
};

Code Examples

Complete Controller Example

import { Request, Response } from 'express';
import { User } from '../models/User';
import { ResponseHelper } from '../utils/responseHelper';
import { AppError } from '../errors/AppError';
import ActivityLogger from '../utils/activityLogger';

export class UserController {
    static async getProfile(req: Request, res: Response) {
        try {
            const userId = (req as any).user.id;
            const user = await User.findById(userId).select('-password');
            
            if (!user) {
                throw new AppError('User not found', 404);
            }
            
            ActivityLogger.logAuthorizationEvent('GRANTED', 'User profile accessed', req, {
                userId: user.id,
                action: 'profile_view'
            });
            
            return ResponseHelper.success(res, user, 'Profile retrieved successfully');
        } catch (error) {
            ActivityLogger.logError(error, req, res);
            return ResponseHelper.error(res, 'Failed to retrieve profile', 500, error);
        }
    }
    
    static async updateProfile(req: Request, res: Response) {
        try {
            const userId = (req as any).user.id;
            const { name, email } = req.body;
            
            const user = await User.findByIdAndUpdate(
                userId,
                { name, email },
                { new: true, runValidators: true }
            ).select('-password');
            
            if (!user) {
                throw new AppError('User not found', 404);
            }
            
            ActivityLogger.logAppControlEvent('PROFILE_UPDATE', 'User profile updated', {
                userId: user.id,
                updatedFields: Object.keys(req.body)
            }, req);
            
            return ResponseHelper.success(res, user, 'Profile updated successfully');
        } catch (error) {
            ActivityLogger.logError(error, req, res);
            return ResponseHelper.error(res, 'Failed to update profile', 500, error);
        }
    }
}

Route Configuration

import express from 'express';
import { UserController } from '../controllers/UserController';
import { authMiddleware } from '../middleware/authMiddleware';
import { activityLogMiddleware } from '../middleware/activityLogMiddleware';

const router = express.Router();

// Apply activity logging to all routes
router.use(activityLogMiddleware);

// Public routes
router.post('/auth/register', UserController.register);
router.post('/auth/login', UserController.login);

// Protected routes (require authentication)
router.use(authMiddleware);

router.get('/users/profile', UserController.getProfile);
router.put('/users/profile', UserController.updateProfile);

export default router;