AzuraJS Logo
AzuraJSFramework
v2.2 Beta

Type Extensions

Extend Request, Response, and AzuraClient with custom properties

Type Extensions 🔧

AzuraJS allows you to extend core types (Request, Response, AzuraClient) with custom properties and methods, similar to Express but with full TypeScript support.

Quick Start 🚀

TypeScript

// types/express.d.ts
declare module 'azura' {
  interface RequestServer {
    user?: { id: string; name: string };
    session?: any;
  }
  
  interface ResponseServer {
    sendSuccess(data: any): this;
  }
}

// Implementation
app.use((req, res, next) => {
  res.sendSuccess = function(data: any) {
    return this.status(200).json({ success: true, data });
  };
  next();
});

// Usage - now fully typed!
app.get('/data', (req, res) => {
  res.sendSuccess({ message: 'Hello' });  // TypeScript knows about sendSuccess!
});

JavaScript

// No types needed - just extend!
app.use((req, res, next) => {
  res.sendSuccess = function(data) {
    return this.status(200).json({ success: true, data });
  };
  next();
});

Extending Request 📥

Adding User Property

// types.ts
declare module 'azura' {
  interface RequestServer {
    user?: {
      id: string;
      email: string;
      role: 'admin' | 'user';
    };
    isAuthenticated(): boolean;
  }
}

// middleware/auth.ts
app.use((req, res, next) => {
  const token = req.header('authorization')?.replace('Bearer ', '');
  
  if (token) {
    req.user = decodeToken(token);
  }
  
  req.isAuthenticated = function() {
    return !!this.user;
  };
  
  next();
});

// routes/protected.ts
app.get('/profile', (req, res) => {
  if (!req.isAuthenticated()) {  // TypeScript knows this method!
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  res.json({ user: req.user });  // TypeScript knows user property!
});

Adding Session

declare module 'azura' {
  interface RequestServer {
    session?: {
      id: string;
      data: Record<string, any>;
    };
    startSession(data?: any): void;
    destroySession(): void;
  }
}

const sessions = new Map();

app.use((req, res, next) => {
  const sessionId = req.cookies['session-id'];
  
  if (sessionId && sessions.has(sessionId)) {
    req.session = sessions.get(sessionId);
  }
  
  req.startSession = function(data = {}) {
    const id = generateId();
    this.session = { id, data };
    sessions.set(id, this.session);
    res.cookie('session-id', id, { httpOnly: true });
  };
  
  req.destroySession = function() {
    if (this.session) {
      sessions.delete(this.session.id);
      this.session = undefined;
      res.clearCookie('session-id');
    }
  };
  
  next();
});

Extending Response 📤

Custom Response Methods

declare module 'azura' {
  interface ResponseServer {
    sendSuccess(data: any, message?: string): this;
    sendError(message: string, code?: number): this;
    paginate(data: any[], page: number, limit: number): this;
    notFound(message?: string): this;
    badRequest(message?: string): this;
  }
}

app.use((req, res, next) => {
  res.sendSuccess = function(data: any, message?: string) {
    return this.status(200).json({
      success: true,
      message: message || 'Request successful',
      data,
      timestamp: new Date().toISOString()
    });
  };
  
  res.sendError = function(message: string, code = 400) {
    return this.status(code).json({
      success: false,
      error: message,
      timestamp: new Date().toISOString()
    });
  };
  
  res.paginate = function(data: any[], page: number, limit: number) {
    const startIndex = (page - 1) * limit;
    const endIndex = page * limit;
    
    return this.status(200).json({
      success: true,
      data: data.slice(startIndex, endIndex),
      pagination: {
        page,
        limit,
        total: data.length,
        totalPages: Math.ceil(data.length / limit),
        hasNext: endIndex < data.length,
        hasPrev: page > 1
      }
    });
  };
  
  res.notFound = function(message = 'Not found') {
    return this.status(404).json({ error: message });
  };
  
  res.badRequest = function(message = 'Bad request') {
    return this.status(400).json({ error: message });
  };
  
  next();
});

// Usage
app.get('/users', (req, res) => {
  const users = getUsers();
  res.sendSuccess(users);  // Fully typed!
});

app.get('/user/:id', (req, res) => {
  const user = findUser(req.params.id);
  if (!user) {
    return res.notFound('User not found');
  }
  res.sendSuccess(user);
});

Extending AzuraClient 🚀

Adding Health Check

declare module 'azura' {
  interface AzuraClient {
    health(path?: string): void;
    version(version: string, path?: string): void;
    enableCors(options?: CorsOptions): void;
  }
}

// Extend prototype
import { AzuraClient } from 'azura';

AzuraClient.prototype.health = function(path = '/health') {
  this.get(path, (req, res) => {
    res.json({
      status: 'healthy',
      uptime: process.uptime(),
      memory: process.memoryUsage(),
      timestamp: Date.now()
    });
  });
};

AzuraClient.prototype.version = function(version: string, path = '/version') {
  this.get(path, (req, res) => {
    res.json({ version });
  });
};

AzuraClient.prototype.enableCors = function(options = {}) {
  const { origin = '*', methods = 'GET,POST,PUT,DELETE', headers = 'Content-Type,Authorization' } = options;
  
  this.use((req, res, next) => {
    res.set('Access-Control-Allow-Origin', origin);
    res.set('Access-Control-Allow-Methods', methods);
    res.set('Access-Control-Allow-Headers', headers);
    
    if (req.method === 'OPTIONS') {
      return res.status(204).send();
    }
    
    next();
  });
};

// Usage
const app = new AzuraClient();
app.health();              // Adds /health endpoint
app.version('1.0.0');      // Adds /version endpoint
app.enableCors();          // Enables CORS

JavaScript Extensions 📜

No TypeScript? No problem! Extend in pure JavaScript:

const { AzuraClient } = require('azura');

const app = new AzuraClient();

// Method 1: Extend in middleware
app.use((req, res, next) => {
  // Add to response
  res.sendSuccess = function(data) {
    return this.status(200).json({ success: true, data });
  };
  
  // Add to request
  req.isLocalhost = function() {
    return this.ip === '127.0.0.1' || this.ip === '::1';
  };
  
  next();
});

// Method 2: Extend prototype
AzuraClient.prototype.health = function(path) {
  const healthPath = path || '/health';
  this.get(healthPath, (req, res) => {
    res.json({
      status: 'ok',
      uptime: process.uptime()
    });
  });
};

app.health(); // Now available!

Practical Examples 💡

Database Access

declare module 'azura' {
  interface RequestServer {
    db: PrismaClient;  // or any ORM
  }
}

const prisma = new PrismaClient();

app.use((req, res, next) => {
  req.db = prisma;
  next();
});

// Now use req.db anywhere!
app.get('/users', async (req, res) => {
  const users = await req.db.user.findMany();
  res.json(users);
});

Request Validation Helper

declare module 'azura' {
  interface RequestServer {
    validate(schema: any): any;
    requireFields(...fields: string[]): any;
  }
}

app.use((req, res, next) => {
  req.validate = function(schema: any) {
    const { error, value } = schema.validate(this.body);
    if (error) throw new Error(error.message);
    return value;
  };
  
  req.requireFields = function(...fields: string[]) {
    const missing = fields.filter(f => !this.body[f]);
    if (missing.length > 0) {
      throw new Error(`Missing fields: ${missing.join(', ')}`);
    }
    return this.body;
  };
  
  next();
});

// Usage
app.post('/create', (req, res) => {
  try {
    req.requireFields('name', 'email');
    const data = req.body;
    // Create resource...
    res.sendSuccess(data);
  } catch (error) {
    res.badRequest(error.message);
  }
});

Response Timing

declare module 'azura' {
  interface ResponseServer {
    _startTime?: number;
  }
}

app.use((req, res, next) => {
  res._startTime = Date.now();
  
  const originalJson = res.json.bind(res);
  res.json = function(data: any) {
    const responseTime = Date.now() - (this._startTime || 0);
    this.set('X-Response-Time', `${responseTime}ms`);
    
    if (typeof data === 'object' && data !== null) {
      data._meta = {
        responseTime: `${responseTime}ms`,
        timestamp: new Date().toISOString()
      };
    }
    
    return originalJson(data);
  };
  
  next();
});

Best Practices 💎

  1. Always declare types in a .d.ts file
  2. Use module augmentation (declare module 'azura')
  3. Implement in middleware or at application start
  4. Keep extensions minimal - don't bloat core objects
  5. Document custom properties for team members
  6. Use namespaces to avoid conflicts
  7. Test extensions thoroughly

Complete Example 🎯

// types/azura-extensions.d.ts
declare module 'azura' {
  interface RequestServer {
    user?: User;
    session?: Session;
    isAuthenticated(): boolean;
    db: PrismaClient;
  }
  
  interface ResponseServer {
    sendSuccess(data: any, message?: string): this;
    sendError(message: string, code?: number): this;
    notFound(message?: string): this;
  }
  
  interface AzuraClient {
    health(path?: string): void;
  }
}

// app.ts
import { AzuraClient } from 'azura';
import { PrismaClient } from '@prisma/client';

const app = new AzuraClient();
const prisma = new PrismaClient();

// Setup extensions
import './middleware/extensions';

// Enable health check
app.health();

// Use typed extensions
app.get('/users', async (req, res) => {
  if (!req.isAuthenticated()) {
    return res.notFound();
  }
  
  const users = await req.db.user.findMany();
  res.sendSuccess(users, 'Users retrieved');
});

API Reference 📖

Extendable Interfaces

  • RequestServer - HTTP request object
  • ResponseServer - HTTP response object
  • AzuraClient - Main application class

Extension Methods

  • declare module 'azura' - Module augmentation
  • interface RequestServer extends ... - Extend request
  • interface ResponseServer extends ... - Extend response

Examples 📚

Check out complete examples:

On this page