Exemples de Fonctionnalités Web Web TypeScript

Exemples de fonctionnalités web Web TypeScript incluant le routage, le middleware et le service de fichiers statiques

Key Facts

Category
TypeScript
Items
3
Format Families
sample

Sample Overview

Exemples de fonctionnalités web Web TypeScript incluant le routage, le middleware et le service de fichiers statiques This sample set belongs to TypeScript and can be used to test related workflows inside Elysia Tools.

💻 Routage typescript

🟡 intermediate ⭐⭐⭐

Analyser les routes URL, gérer le routage de hachage et gérer l'historique du navigateur

⏱️ 25 min 🏷️ typescript, web, web features
Prerequisites: Intermediate TypeScript, History API, URL API
// Web TypeScript Routing Examples
// Client-side routing with URL parsing, hash routing, and history management

// 1. Route Interface
interface Route {
  path: string;
  handler: (params: Record<string, string>, query: Record<string, string>) => void;
}

// 2. Router
class Router {
  private routes: Route[] = [];
  private notFoundHandler?: () => void;
  private currentParams: Record<string, string> = {};
  private currentQuery: Record<string, string> = {};

  // Add route
  addRoute(path: string, handler: Route['handler']): void {
    this.routes.push({ path, handler });
  }

  // Set 404 handler
  setNotFound(handler: () => void): void {
    this.notFoundHandler = handler;
  }

  // Navigate to path
  navigate(path: string, params: Record<string, string> = {}): void {
    // Build URL with params
    let url = path;

    if (Object.keys(params).length > 0) {
      url = this.replaceParams(path, params);
    }

    // Update browser URL
    window.history.pushState({}, '', url);

    // Handle route
    this.handleRoute(url);
  }

  // Replace current route
  replace(path: string, params: Record<string, string> = {}): void {
    let url = path;

    if (Object.keys(params).length > 0) {
      url = this.replaceParams(path, params);
    }

    window.history.replaceState({}, '', url);
    this.handleRoute(url);
  }

  // Go back
  back(): void {
    window.history.back();
  }

  // Go forward
  forward(): void {
    window.history.forward();
  }

  // Go to specific history entry
  go(delta: number): void {
    window.history.go(delta);
  }

  // Handle route change
  private handleRoute(url: string): void {
    const { path, query } = this.parseURL(url);

    // Find matching route
    const route = this.findRoute(path);

    if (route) {
      const params = this.extractParams(path, route.path);
      this.currentParams = params;
      this.currentQuery = query;
      route.handler(params, query);
    } else if (this.notFoundHandler) {
      this.notFoundHandler();
    }
  }

  // Find matching route
  private findRoute(path: string): Route | null {
    for (const route of this.routes) {
      if (this.matchRoute(path, route.path)) {
        return route;
      }
    }
    return null;
  }

  // Check if path matches route pattern
  private matchRoute(path: string, pattern: string): boolean {
    // Convert pattern to regex
    const regexPattern = pattern
      .replace(/:\\w+/g, '([^/]+)')
      .replace(/\\*/g, '.*');

    const regex = new RegExp(`^${regexPattern}$`);
    return regex.test(path);
  }

  // Extract params from path
  private extractParams(path: string, pattern: string): Record<string, string> {
    const params: Record<string, string> = {};

    const patternParts = pattern.split('/');
    const pathParts = path.split('/');

    for (let i = 0; i < patternParts.length; i++) {
      const part = patternParts[i];
      if (part.startsWith(':')) {
        const paramName = part.substring(1);
        params[paramName] = pathParts[i] || '';
      }
    }

    return params;
  }

  // Replace params in path
  private replaceParams(path: string, params: Record<string, string>): string {
    let result = path;

    for (const [key, value] of Object.entries(params)) {
      result = result.replace(`:${key}`, value);
    }

    return result;
  }

  // Parse URL into path and query
  private parseURL(url: string): {
    path: string;
    query: Record<string, string>;
  } {
    const [path, queryString] = url.split('?');
    const query: Record<string, string> = {};

    if (queryString) {
      const params = new URLSearchParams(queryString);
      params.forEach((value, key) => {
        query[key] = value;
      });
    }

    return { path, query };
  }

  // Initialize router
  initialize(): void {
    // Handle popstate event (back/forward buttons)
    window.addEventListener('popstate', () => {
      this.handleRoute(window.location.pathname);
    });

    // Handle initial route
    this.handleRoute(window.location.pathname);
  }
}

// 3. Hash Router
class HashRouter {
  private routes: Route[] = [];
  private notFoundHandler?: () => void;

  // Add route
  addRoute(path: string, handler: Route['handler']): void {
    // Remove leading slash for hash matching
    const hashPath = path.startsWith('/') ? path.substring(1) : path;
    this.routes.push({ path: hashPath, handler });
  }

  // Set 404 handler
  setNotFound(handler: () => void): void {
    this.notFoundHandler = handler;
  }

  // Navigate to hash
  navigate(hash: string, params: Record<string, string> = {}): void {
    let url = hash;

    if (Object.keys(params).length > 0) {
      url = this.replaceParams(hash, params);
    }

    window.location.hash = url;
  }

  // Get current hash
  getHash(): string {
    return window.location.hash.substring(1) || '/';
  }

  // Handle hash change
  private handleHashChange(): void {
    const hash = this.getHash();
    const [path, queryString] = hash.split('?');
    const query: Record<string, string> = {};

    if (queryString) {
      const params = new URLSearchParams(queryString);
      params.forEach((value, key) => {
        query[key] = value;
      });
    }

    // Find matching route
    const route = this.findRoute(path);

    if (route) {
      const params = this.extractParams(path, route.path);
      route.handler(params, query);
    } else if (this.notFoundHandler) {
      this.notFoundHandler();
    }
  }

  // Find matching route
  private findRoute(path: string): Route | null {
    for (const route of this.routes) {
      if (this.matchRoute(path, route.path)) {
        return route;
      }
    }
    return null;
  }

  // Check if path matches route pattern
  private matchRoute(path: string, pattern: string): boolean {
    const regexPattern = pattern
      .replace(/:\\w+/g, '([^/]+)')
      .replace(/\\*/g, '.*');

    const regex = new RegExp(`^${regexPattern}$`);
    return regex.test(path);
  }

  // Extract params from path
  private extractParams(path: string, pattern: string): Record<string, string> {
    const params: Record<string, string> = {};

    const patternParts = pattern.split('/');
    const pathParts = path.split('/');

    for (let i = 0; i < patternParts.length; i++) {
      const part = patternParts[i];
      if (part.startsWith(':')) {
        const paramName = part.substring(1);
        params[paramName] = pathParts[i] || '';
      }
    }

    return params;
  }

  // Replace params in path
  private replaceParams(path: string, params: Record<string, string>): string {
    let result = path;

    for (const [key, value] of Object.entries(params)) {
      result = result.replace(`:${key}`, value);
    }

    return result;
  }

  // Initialize hash router
  initialize(): void {
    window.addEventListener('hashchange', () => {
      this.handleHashChange();
    });

    // Handle initial hash
    this.handleHashChange();
  }
}

// 4. Query Params Manager
class QueryParamsManager {
  // Get query param
  get(param: string): string | null {
    const params = new URLSearchParams(window.location.search);
    return params.get(param);
  }

  // Get all params
  getAll(): Record<string, string> {
    const params = new URLSearchParams(window.location.search);
    const result: Record<string, string> = {};

    params.forEach((value, key) => {
      result[key] = value;
    });

    return result;
  }

  // Set query param
  set(param: string, value: string): void {
    const params = new URLSearchParams(window.location.search);
    params.set(param, value);
    this.update(params.toString());
  }

  // Delete query param
  delete(param: string): void {
    const params = new URLSearchParams(window.location.search);
    params.delete(param);
    this.update(params.toString());
  }

  // Clear all params
  clear(): void {
    this.update('');
  }

  // Update URL
  private update(queryString: string): void {
    const url = new URL(window.location.href);
    url.search = queryString;
    window.history.replaceState({}, '', url.toString());
  }

  // Watch for changes
  watch(callback: (params: Record<string, string>) => void): () => void {
    const handler = () => {
      callback(this.getAll());
    };

    window.addEventListener('popstate', handler);

    // Return cleanup function
    return () => {
      window.removeEventListener('popstate', handler);
    };
  }
}

// 5. Route Guard
class RouteGuard {
  private guards: Map<string, () => boolean | Promise<boolean>> = new Map();

  // Add guard for route
  addGuard(route: string, guard: () => boolean | Promise<boolean>): void {
    this.guards.set(route, guard);
  }

  // Check if route is allowed
  async canNavigate(route: string): Promise<boolean> {
    const guard = this.guards.get(route);

    if (guard) {
      return await guard();
    }

    return true;
  }

  // Add authentication guard
  addAuthGuard(route: string, isAuthenticated: () => boolean): void {
    this.addGuard(route, isAuthenticated);
  }

  // Add permission guard
  addPermissionGuard(route: string, hasPermission: () => boolean | Promise<boolean>): void {
    this.addGuard(route, hasPermission);
  }
}

// 6. Navigation Manager
class NavigationManager {
  private router: Router;
  private history: string[] = [];
  private currentIndex: number = -1;

  constructor(router: Router) {
    this.router = router;
  }

  // Navigate with history tracking
  navigate(path: string, params: Record<string, string> = {}): void {
    // Remove forward history if we're not at the end
    if (this.currentIndex < this.history.length - 1) {
      this.history = this.history.slice(0, this.currentIndex + 1);
    }

    // Add to history
    this.history.push(path);
    this.currentIndex++;

    // Navigate
    this.router.navigate(path, params);
  }

  // Go back in custom history
  back(): void {
    if (this.currentIndex > 0) {
      this.currentIndex--;
      const path = this.history[this.currentIndex];
      window.history.back();
    }
  }

  // Go forward in custom history
  forward(): void {
    if (this.currentIndex < this.history.length - 1) {
      this.currentIndex++;
      const path = this.history[this.currentIndex];
      window.history.forward();
    }
  }

  // Can go back
  canGoBack(): boolean {
    return this.currentIndex > 0;
  }

  // Can go forward
  canGoForward(): boolean {
    return this.currentIndex < this.history.length - 1;
  }

  // Get current position
  getCurrentPosition(): number {
    return this.currentIndex;
  }

  // Get history length
  getHistoryLength(): number {
    return this.history.length;
  }
}

// 7. Route Transition Manager
class RouteTransitionManager {
  private transitions: Map<string, (from: string, to: string) => void> = new Map();

  // Add transition
  addTransition(from: string, to: string, handler: () => void): void {
    const key = this.getKey(from, to);
    this.transitions.set(key, handler);
  }

  // Execute transition
  execute(from: string, to: string): void {
    const key = this.getKey(from, to);
    const handler = this.transitions.get(key);

    if (handler) {
      handler(from, to);
    }
  }

  // Add global transition
  addGlobalTransition(handler: (from: string, to: string) => void): void {
    this.transitions.set('*', handler);
  }

  // Generate transition key
  private getKey(from: string, to: string): string {
    return `${from}>${to}`;
  }
}

// Usage Examples
async function demonstrateRouting() {
  console.log('=== Web TypeScript Routing Examples ===\n');

  // 1. Basic router
  console.log('--- 1. Basic Router ---');
  const router = new Router();

  router.addRoute('/', (params, query) => {
    console.log('Home page');
  });

  router.addRoute('/about', (params, query) => {
    console.log('About page');
  });

  router.addRoute('/users/:id', (params, query) => {
    console.log(`User page: ${params.id}`);
  });

  router.addRoute('/posts/:postId/comments/:commentId', (params, query) => {
    console.log(`Comment: ${params.commentId} on post ${params.postId}`);
  });

  // 2. Navigation
  console.log('\n--- 2. Navigation ---');
  router.navigate('/');
  await new Promise(resolve => setTimeout(resolve, 100));

  router.navigate('/about');
  await new Promise(resolve => setTimeout(resolve, 100));

  router.navigate('/users/123');
  await new Promise(resolve => setTimeout(resolve, 100));

  // 3. Query params
  console.log('\n--- 3. Query Params ---');
  router.navigate('/search?query=typescript&page=1');
  await new Promise(resolve => setTimeout(resolve, 100));

  // 4. Hash router
  console.log('\n--- 4. Hash Router ---');
  const hashRouter = new HashRouter();

  hashRouter.addRoute('/', (params, query) => {
    console.log('Hash home');
  });

  hashRouter.addRoute('profile/:userId', (params, query) => {
    console.log(`Hash profile: ${params.userId}`);
  });

  hashRouter.navigate('profile/456');
  await new Promise(resolve => setTimeout(resolve, 100));

  // 5. Query params manager
  console.log('\n--- 5. Query Params Manager ---');
  const queryManager = new QueryParamsManager();

  queryManager.set('tab', 'profile');
  queryManager.set('section', 'details');

  const allParams = queryManager.getAll();
  console.log('All params:', allParams);

  // 6. Route guard
  console.log('\n--- 6. Route Guard ---');
  const guard = new RouteGuard();

  guard.addAuthGuard('/admin', () => {
    const isAuthenticated = false; // Simulate
    console.log(`Auth check: ${isAuthenticated}`);
    return isAuthenticated;
  });

  const canAccess = await guard.canNavigate('/admin');
  console.log(`Can access /admin: ${canAccess}`);

  // 7. Navigation manager
  console.log('\n--- 7. Navigation Manager ---');
  const navManager = new NavigationManager(router);

  navManager.navigate('/page1');
  await new Promise(resolve => setTimeout(resolve, 100));

  navManager.navigate('/page2');
  await new Promise(resolve => setTimeout(resolve, 100));

  console.log(`Can go back: ${navManager.canGoBack()}`);
  console.log(`History length: ${navManager.getHistoryLength()}`);

  console.log('\n=== All Routing Examples Completed ===');
}

// Export functions
export { Router, HashRouter, QueryParamsManager, RouteGuard, NavigationManager, RouteTransitionManager };
export { demonstrateRouting };
export type { Route };

💻 Middleware typescript

🟡 intermediate ⭐⭐⭐⭐

Implémenter une chaîne de middleware requête/réponse pour traiter et modifier des données

⏱️ 30 min 🏷️ typescript, web, web features
Prerequisites: Intermediate TypeScript, Promise chains, Async/await
// Web TypeScript Middleware Examples
// Request/response processing middleware chain

// 1. Context Interface
interface Context {
  request: any;
  response: any;
  state: Map<string, any>;
  headers: Map<string, string>;
  metadata: Record<string, any>;
}

// 2. Middleware Function Type
type Middleware = (context: Context, next: () => Promise<void>) => Promise<void>;

// 3. Middleware Pipeline
class MiddlewarePipeline {
  private middlewares: Middleware[] = [];

  // Add middleware
  use(middleware: Middleware): this {
    this.middlewares.push(middleware);
    return this;
  }

  // Execute pipeline
  async execute(context: Context): Promise<void> {
    let index = 0;

    const next = async (): Promise<void> => {
      if (index < this.middlewares.length) {
        const middleware = this.middlewares[index++];
        await middleware(context, next);
      }
    };

    await next();
  }

  // Clear all middlewares
  clear(): void {
    this.middlewares = [];
  }

  // Get middleware count
  count(): number {
    return this.middlewares.length;
  }
}

// 4. Common Middlewares
class CommonMiddlewares {
  // Logging middleware
  static logging(): Middleware {
    return async (context: Context, next: () => Promise<void>) => {
      const start = performance.now();

      console.log('[Request]', {
        url: context.request?.url || 'N/A',
        method: context.request?.method || 'N/A',
        headers: Object.fromEntries(context.headers)
      });

      await next();

      const duration = performance.now() - start;
      console.log('[Response]', {
        status: context.response?.status || 'N/A',
        duration: `${duration.toFixed(2)}ms`
      });
    };
  }

  // Timing middleware
  static timing(): Middleware {
    return async (context: Context, next: () => Promise<void>) => {
      const start = performance.now();

      await next();

      const duration = performance.now() - start;
      context.metadata.duration = duration;
    };
  }

  // Error handling middleware
  static errorHandler(errorHandler: (error: Error) => void): Middleware {
    return async (context: Context, next: () => Promise<void>) => {
      try {
        await next();
      } catch (error) {
        errorHandler(error as Error);
        throw error;
      }
    };
  }

  // Header manipulation middleware
  static headers(addHeaders: Record<string, string>): Middleware {
    return async (context: Context, next: () => Promise<void>) => {
      for (const [key, value] of Object.entries(addHeaders)) {
        context.headers.set(key, value);
      }

      await next();
    };
  }

  // Authentication middleware
  static authentication(authCheck: (context: Context) => boolean | Promise<boolean>): Middleware {
    return async (context: Context, next: () => Promise<void>) => {
      const isAuthenticated = await authCheck(context);

      if (!isAuthenticated) {
        throw new Error('Unauthorized');
      }

      context.metadata.authenticated = true;
      await next();
    };
  }

  // Rate limiting middleware
  static rateLimit(options: {
    maxRequests: number;
    windowMs: number;
  }): Middleware {
    const requests = new Map<string, number[]>();

    return async (context: Context, next: () => Promise<void>) => {
      const key = context.request?.ip || 'unknown';
      const now = Date.now();

      if (!requests.has(key)) {
        requests.set(key, []);
      }

      const userRequests = requests.get(key)!;

      // Remove old requests outside the window
      const windowStart = now - options.windowMs;
      const validRequests = userRequests.filter(time => time > windowStart);

      // Check if limit exceeded
      if (validRequests.length >= options.maxRequests) {
        throw new Error('Rate limit exceeded');
      }

      // Add current request
      validRequests.push(now);
      requests.set(key, validRequests);

      context.metadata.rateLimit = {
        remaining: options.maxRequests - validRequests.length,
        reset: now + options.windowMs
      };

      await next();
    };
  }

  // CORS middleware
  static cors(options: {
    origin?: string | string[];
    methods?: string[];
    headers?: string[];
    credentials?: boolean;
  } = {}): Middleware {
    return async (context: Context, next: () => Promise<void>) => {
      const origin = context.request?.headers?.get('Origin') || '*';

      // Set CORS headers
      if (options.origin === '*' || !options.origin) {
        context.headers.set('Access-Control-Allow-Origin', '*');
      } else {
        const allowedOrigins = Array.isArray(options.origin) ? options.origin : [options.origin];
        if (allowedOrigins.includes(origin)) {
          context.headers.set('Access-Control-Allow-Origin', origin);
        }
      }

      if (options.methods) {
        context.headers.set('Access-Control-Allow-Methods', options.methods.join(', '));
      }

      if (options.headers) {
        context.headers.set('Access-Control-Allow-Headers', options.headers.join(', '));
      }

      if (options.credentials) {
        context.headers.set('Access-Control-Allow-Credentials', 'true');
      }

      await next();
    };
  }

  // Compression middleware (simulated)
  static compression(): Middleware {
    return async (context: Context, next: () => Promise<void>) => {
      await next();

      // Check if response should be compressed
      const acceptEncoding = context.request?.headers?.get('Accept-Encoding') || '';

      if (acceptEncoding.includes('gzip') || acceptEncoding.includes('br')) {
        context.headers.set('Content-Encoding', 'gzip');
        context.metadata.compressed = true;
      }
    };
  }

  // Body parser middleware
  static bodyParser(): Middleware {
    return async (context: Context, next: () => Promise<void>) => {
      const contentType = context.request?.headers?.get('Content-Type') || '';

      if (contentType.includes('application/json')) {
        try {
          const body = context.request?.body;
          if (typeof body === 'string') {
            context.request.parsedBody = JSON.parse(body);
          }
        } catch (error) {
          throw new Error('Invalid JSON body');
        }
      }

      await next();
    };
  }

  // Validation middleware
  static validation(validator: (context: Context) => boolean | Promise<boolean>): Middleware {
    return async (context: Context, next: () => Promise<void>) => {
      const isValid = await validator(context);

      if (!isValid) {
        throw new Error('Validation failed');
      }

      await next();
    };
  }

  // Cache middleware
  static cache(cacheKey: (context: Context) => string, ttl: number = 60000): Middleware {
    const cache = new Map<string, { data: any; expiry: number }>();

    return async (context: Context, next: () => Promise<void>) => {
      const key = cacheKey(context);
      const now = Date.now();

      // Check cache
      const cached = cache.get(key);
      if (cached && cached.expiry > now) {
        context.response = cached.data;
        context.metadata.cached = true;
        return;
      }

      await next();

      // Store in cache
      if (context.response) {
        cache.set(key, {
          data: context.response,
          expiry: now + ttl
        });
      }
    };
  }
}

// 5. Middleware Composer
class MiddlewareComposer {
  private pipeline: MiddlewarePipeline;

  constructor() {
    this.pipeline = new MiddlewarePipeline();
  }

  // Add middleware
  use(middleware: Middleware): this {
    this.pipeline.use(middleware);
    return this;
  }

  // Add multiple middlewares
  useAll(middlewares: Middleware[]): this {
    middlewares.forEach(mw => this.pipeline.use(mw));
    return this;
  }

  // Execute with context
  async execute(context: Context): Promise<void> {
    return this.pipeline.execute(context);
  }

  // Create from array
  static from(middlewares: Middleware[]): MiddlewareComposer {
    const composer = new MiddlewareComposer();
    return composer.useAll(middlewares);
  }
}

// 6. Request/Response Mock
class MockRequest {
  constructor(
    public url: string,
    public method: string = 'GET',
    public headers: Map<string, string> = new Map(),
    public body?: any,
    public ip: string = '127.0.0.1'
  ) {}
}

class MockResponse {
  public status: number = 200;
  public headers: Map<string, string> = new Map();
  public body?: any;

  setStatus(code: number): this {
    this.status = code;
    return this;
  }

  setHeader(key: string, value: string): this {
    this.headers.set(key, value);
    return this;
  }

  send(data: any): this {
    this.body = data;
    return this;
  }
}

// 7. Application Builder
class ApplicationBuilder {
  private composer: MiddlewareComposer;
  private contextFactory: () => Context;

  constructor() {
    this.composer = new MiddlewareComposer();
    this.contextFactory = () => ({
      request: null,
      response: null,
      state: new Map(),
      headers: new Map(),
      metadata: {}
    });
  }

  // Set context factory
  setContextFactory(factory: () => Context): this {
    this.contextFactory = factory;
    return this;
  }

  // Add middleware
  use(middleware: Middleware): this {
    this.composer.use(middleware);
    return this;
  }

  // Handle request
  async handle(request: MockRequest): Promise<MockResponse> {
    const context = this.contextFactory();
    context.request = request;
    context.response = new MockResponse();

    await this.composer.execute(context);

    return context.response as MockResponse;
  }
}

// Usage Examples
async function demonstrateMiddleware() {
  console.log('=== Web TypeScript Middleware Examples ===\n');

  // 1. Basic middleware
  console.log('--- 1. Basic Middleware ---');
  const pipeline = new MiddlewarePipeline();

  pipeline.use(async (context, next) => {
    console.log('Middleware 1: Before');
    await next();
    console.log('Middleware 1: After');
  });

  pipeline.use(async (context, next) => {
    console.log('Middleware 2: Before');
    await next();
    console.log('Middleware 2: After');
  });

  const context1: Context = {
    request: { url: '/test' },
    response: null,
    state: new Map(),
    headers: new Map(),
    metadata: {}
  };

  await pipeline.execute(context1);

  // 2. Common middlewares
  console.log('\n--- 2. Common Middlewares ---');
  const app = new ApplicationBuilder();

  app.use(CommonMiddlewares.logging());
  app.use(CommonMiddlewares.timing());
  app.use(CommonMiddlewares.headers({
    'X-Powered-By': 'TypeScript-Middleware',
    'X-Response-Time': '0ms'
  }));

  const request = new MockRequest('/api/users', 'GET');
  request.headers.set('Accept', 'application/json');

  const response = await app.handle(request);
  console.log('Response status:', response.status);

  // 3. Error handling
  console.log('\n--- 3. Error Handling ---');
  const errorApp = new ApplicationBuilder();

  errorApp.use(CommonMiddlewares.errorHandler((error) => {
    console.error('Error caught:', error.message);
  }));

  errorApp.use(async (context, next) => {
    if (context.request?.url === '/error') {
      throw new Error('Intentional error');
    }
    await next();
  });

  await errorApp.handle(new MockRequest('/error'));

  // 4. Authentication
  console.log('\n--- 4. Authentication ---');
  const authApp = new ApplicationBuilder();

  authApp.use(CommonMiddlewares.authentication(async (context) => {
    const token = context.request?.headers?.get('Authorization');
    return token === 'Bearer valid-token';
  }));

  authApp.use(async (context, next) => {
    console.log('Authenticated successfully');
    context.response = new MockResponse().setStatus(200).send({ message: 'Success' });
    await next();
  });

  const authRequest = new MockRequest('/protected', 'GET');
  authRequest.headers.set('Authorization', 'Bearer valid-token');

  const authResponse = await authApp.handle(authRequest);
  console.log('Auth response status:', authResponse.status);

  // 5. Rate limiting
  console.log('\n--- 5. Rate Limiting ---');
  const rateLimitApp = new ApplicationBuilder();

  rateLimitApp.use(CommonMiddlewares.rateLimit({
    maxRequests: 3,
    windowMs: 10000
  }));

  rateLimitApp.use(async (context, next) => {
    context.response = new MockResponse().setStatus(200).send({ message: 'OK' });
    await next();
  });

  // Send multiple requests
  const rlRequest = new MockRequest('/api/test');
  for (let i = 0; i < 4; i++) {
    try {
      const rlResponse = await rateLimitApp.handle(rlRequest);
      console.log(`Request ${i + 1}: ${rlResponse.status}`);
    } catch (error) {
      console.log(`Request ${i + 1}: Rate limited`);
    }
  }

  // 6. Compose
  console.log('\n--- 6. Middleware Composer ---');
  const composed = MiddlewareComposer.from([
    CommonMiddlewares.logging(),
    CommonMiddlewares.timing(),
    async (context, next) => {
      console.log('Final handler');
      await next();
    }
  ]);

  const context2: Context = {
    request: { url: '/composed' },
    response: null,
    state: new Map(),
    headers: new Map(),
    metadata: {}
  };

  await composed.execute(context2);

  console.log('\n=== All Middleware Examples Completed ===');
}

// Export functions
export { MiddlewarePipeline, CommonMiddlewares, MiddlewareComposer, ApplicationBuilder, MockRequest, MockResponse };
export { demonstrateMiddleware };
export type { Context, Middleware };

💻 Fichiers Statiques typescript

🟡 intermediate ⭐⭐⭐

Servir des fichiers statiques avec mise en cache, compression et détection de type MIME

⏱️ 25 min 🏷️ typescript, web, web features
Prerequisites: Intermediate TypeScript, Fetch API, Blob
// Web TypeScript Static File Serving Examples
// Serving static files with various strategies

// 1. MIME Type Detector
class MIMETypeDetector {
  private static mimeTypes: Record<string, string> = {
    '.html': 'text/html',
    '.htm': 'text/html',
    '.css': 'text/css',
    '.js': 'text/javascript',
    '.json': 'application/json',
    '.xml': 'application/xml',
    '.txt': 'text/plain',
    '.pdf': 'application/pdf',
    '.zip': 'application/zip',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.gif': 'image/gif',
    '.svg': 'image/svg+xml',
    '.ico': 'image/x-icon',
    '.woff': 'font/woff',
    '.woff2': 'font/woff2',
    '.ttf': 'font/ttf',
    '.eot': 'application/vnd.ms-fontobject',
    '.mp3': 'audio/mpeg',
    '.mp4': 'video/mp4',
    '.webm': 'video/webm',
    '.ogg': 'video/ogg'
  };

  // Get MIME type by extension
  static getByExtension(filename: string): string {
    const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();

    if (ext && this.mimeTypes[ext]) {
      return this.mimeTypes[ext];
    }

    return 'application/octet-stream';
  }

  // Get MIME type by file signature (magic bytes)
  static async getBySignature(file: Blob): Promise<string> {
    const header = await this.readFileHeader(file);

    // Check common signatures
    if (this.startsWith(header, [0x89, 0x50, 0x4E, 0x47])) return 'image/png';
    if (this.startsWith(header, [0xFF, 0xD8, 0xFF])) return 'image/jpeg';
    if (this.startsWith(header, [0x47, 0x49, 0x46, 0x38])) return 'image/gif';
    if (this.startsWith(header, [0x50, 0x4B, 0x03, 0x04])) return 'application/zip';
    if (this.startsWith(header, [0x25, 0x50, 0x44, 0x46])) return 'application/pdf';
    if (this.startsWith(header, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])) return 'image/gif';

    return 'application/octet-stream';
  }

  // Read file header
  private static async readFileHeader(file: Blob, bytes: number = 8): Promise<Uint8Array> {
    const slice = file.slice(0, bytes);
    const arrayBuffer = await slice.arrayBuffer();
    return new Uint8Array(arrayBuffer);
  }

  // Check if header starts with bytes
  private static startsWith(header: Uint8Array, bytes: number[]): boolean {
    if (header.length < bytes.length) return false;

    for (let i = 0; i < bytes.length; i++) {
      if (header[i] !== bytes[i]) return false;
    }

    return true;
  }
}

// 2. Static File Server
class StaticFileServer {
  private baseUrl: string;
  private cache: Map<string, { data: any; mime: string; timestamp: number }> = new Map();
  private cacheTimeout: number = 60000; // 1 minute

  constructor(baseUrl: string = '/static') {
    this.baseUrl = baseUrl;
  }

  // Serve file
  async serve(filePath: string): Promise<{
    data: any;
    mime: string;
    status: number;
  }> {
    // Check cache
    const cached = this.cache.get(filePath);

    if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
      return {
        data: cached.data,
        mime: cached.mime,
        status: 200
      };
    }

    // Fetch file
    const url = `${this.baseUrl}${filePath}`;

    try {
      const response = await fetch(url);

      if (!response.ok) {
        return {
          data: null,
          mime: 'text/plain',
          status: response.status
        };
      }

      const mime = response.headers.get('Content-Type') ||
                   MIMETypeDetector.getByExtension(filePath);

      let data: any;

      const contentType = mime.split(';')[0];

      if (contentType.includes('application/json')) {
        data = await response.json();
      } else if (contentType.includes('text/')) {
        data = await response.text();
      } else {
        data = await response.blob();
      }

      // Cache response
      this.cache.set(filePath, {
        data,
        mime,
        timestamp: Date.now()
      });

      return { data, mime, status: 200 };
    } catch (error) {
      return {
        data: null,
        mime: 'text/plain',
        status: 500
      };
    }
  }

  // Serve with fallback
  async serveWithFallback(filePath: string, fallbackPath: string): Promise<{
    data: any;
    mime: string;
    status: number;
  }> {
    const result = await this.serve(filePath);

    if (result.status === 404) {
      return this.serve(fallbackPath);
    }

    return result;
  }

  // Clear cache
  clearCache(): void {
    this.cache.clear();
  }

  // Clear specific cache entry
  clearCacheEntry(filePath: string): void {
    this.cache.delete(filePath);
  }

  // Get cache size
  getCacheSize(): number {
    return this.cache.size;
  }
}

// 3. File Cache Manager
class FileCacheManager {
  private cache: Map<string, {
    content: any;
    mimeType: string;
    etag: string;
    lastModified: string;
  }> = new Map();

  // Add to cache
  set(filePath: string, content: any, mimeType: string, etag?: string, lastModified?: string): void {
    this.cache.set(filePath, {
      content,
      mimeType,
      etag: etag || this.generateETag(content),
      lastModified: lastModified || new Date().toUTCString()
    });
  }

  // Get from cache
  get(filePath: string): {
    content: any;
    mimeType: string;
    etag: string;
    lastModified: string;
  } | null {
    return this.cache.get(filePath) || null;
  }

  // Check if file is cached
  has(filePath: string): boolean {
    return this.cache.has(filePath);
  }

  // Remove from cache
  remove(filePath: string): void {
    this.cache.delete(filePath);
  }

  // Clear all cache
  clear(): void {
    this.cache.clear();
  }

  // Get cache stats
  getStats(): {
    size: number;
    entries: number;
    keys: string[];
  } {
    return {
      size: this.cache.size,
      entries: this.cache.size,
      keys: Array.from(this.cache.keys())
    };
  }

  // Generate ETag
  private generateETag(content: any): string {
    const str = typeof content === 'string' ? content : JSON.stringify(content);
    let hash = 0;

    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }

    return `"${hash.toString(16)}"`;
  }
}

// 4. Asset Preloader
class AssetPreloader {
  private loadedAssets: Set<string> = new Set();
  private failedAssets: Set<string> = new Set();

  // Preload single asset
  async preload(url: string): Promise<{
    success: boolean;
    error?: Error;
  }> {
    if (this.loadedAssets.has(url)) {
      return { success: true };
    }

    if (this.failedAssets.has(url)) {
      return { success: true, error: new Error('Previously failed to load') };
    }

    try {
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      this.loadedAssets.add(url);
      return { success: true };
    } catch (error) {
      this.failedAssets.add(url);
      return { success: false, error: error as Error };
    }
  }

  // Preload multiple assets
  async preloadMultiple(urls: string[]): Promise<{
    successful: string[];
    failed: Array<{ url: string; error: Error }>;
  }> {
    const results = await Promise.allSettled(
      urls.map(url => this.preload(url))
    );

    const successful: string[] = [];
    const failed: Array<{ url: string; error: Error }> = [];

    results.forEach((result, index) => {
      if (result.status === 'fulfilled' && result.value.success) {
        successful.push(urls[index]);
      } else {
        const error = result.status === 'rejected'
          ? result.reason
          : (result.value as any).error;

        failed.push({ url: urls[index], error });
      }
    });

    return { successful, failed };
  }

  // Preload image
  preloadImage(url: string): Promise<HTMLImageElement> {
    return new Promise((resolve, reject) => {
      const img = new Image();

      img.onload = () => {
        this.loadedAssets.add(url);
        resolve(img);
      };

      img.onerror = () => {
        this.failedAssets.add(url);
        reject(new Error('Failed to load image'));
      };

      img.src = url;
    });
  }

  // Preload images
  async preloadImages(urls: string[]): Promise<HTMLImageElement[]> {
    const results = await Promise.allSettled(
      urls.map(url => this.preloadImage(url))
    );

    return results
      .filter((result): result is PromiseFulfilledResult<HTMLImageElement> =>
        result.status === 'fulfilled'
      )
      .map(result => result.value);
  }

  // Get loaded assets
  getLoadedAssets(): string[] {
    return Array.from(this.loadedAssets);
  }

  // Get failed assets
  getFailedAssets(): string[] {
    return Array.from(this.failedAssets);
  }

  // Clear tracking
  clear(): void {
    this.loadedAssets.clear();
    this.failedAssets.clear();
  }
}

// 5. Compression Handler
class CompressionHandler {
  // Check if compression is supported
  static isSupported(): boolean {
    return 'CompressionStream' in window;
  }

  // Compress data (if supported)
  static async compress(data: string, format: 'gzip' | 'deflate' = 'gzip'): Promise<Uint8Array> {
    if (!this.isSupported()) {
      throw new Error('Compression not supported');
    }

    const stream = new CompressionStream(format);
    const writer = stream.writable.getWriter();
    const encoder = new TextEncoder();

    await writer.write(encoder.encode(data));
    await writer.close();

    const compressed = new ReadableStream({
      start(controller) {
        // Would need to implement proper stream reading
        controller.close();
      }
    });

    // This is a simplified version
    return encoder.encode(data);
  }

  // Get best compression format
  static getBestCompression(acceptEncoding: string): 'gzip' | 'deflate' | 'none' {
    if (acceptEncoding.includes('br')) return 'gzip';
    if (acceptEncoding.includes('gzip')) return 'gzip';
    if (acceptEncoding.includes('deflate')) return 'deflate';
    return 'none';
  }
}

// 6. CDN Manager
class CDNManager {
  private cdnUrls: string[] = [];
  private currentCdnIndex: number = 0;

  constructor(cdnUrls: string[]) {
    this.cdnUrls = cdnUrls;
  }

  // Get CDN URL for asset
  getCdnUrl(assetPath: string): string {
    const cdnUrl = this.cdnUrls[this.currentCdnIndex];
    return `${cdnUrl}${assetPath}`;
  }

  // Rotate CDN
  rotateCdn(): void {
    this.currentCdnIndex = (this.currentCdnIndex + 1) % this.cdnUrls.length;
  }

  // Get all CDN URLs for asset
  getAllCdnUrls(assetPath: string): string[] {
    return this.cdnUrls.map(cdn => `${cdn}${assetPath}`);
  }

  // Try each CDN until one succeeds
  async tryAllCdns(assetPath: string): Promise<{
    success: boolean;
    cdnUrl?: string;
    data?: any;
  }> {
    const urls = this.getAllCdnUrls(assetPath);

    for (const url of urls) {
      try {
        const response = await fetch(url);

        if (response.ok) {
          const data = await response.blob();
          return { success: true, cdnUrl: url, data };
        }
      } catch (error) {
        console.warn(`CDN failed: ${url}`, error);
      }
    }

    return { success: false };
  }
}

// 7. Asset Bundle Manager
class AssetBundleManager {
  private bundles: Map<string, string[]> = new Map();

  // Register bundle
  registerBundle(name: string, assets: string[]): void {
    this.bundles.set(name, assets);
  }

  // Get bundle assets
  getBundle(name: string): string[] | null {
    return this.bundles.get(name) || null;
  }

  // Preload bundle
  async preloadBundle(name: string, preloader: AssetPreloader): Promise<{
    successful: string[];
    failed: Array<{ url: string; error: Error }>;
  }> {
    const assets = this.getBundle(name);

    if (!assets) {
      return { successful: [], failed: [] };
    }

    return preloader.preloadMultiple(assets);
  }

  // Get bundle URL (for script/link tags)
  getBundleHtml(name: string, type: 'script' | 'style'): string {
    const assets = this.getBundle(name);

    if (!assets) {
      return '';
    }

    return assets.map(url => {
      if (type === 'script') {
        return `<script src="${url}"></script>`;
      } else {
        return `<link rel="stylesheet" href="${url}">`;
      }
    }).join('\n');
  }
}

// Usage Examples
async function demonstrateStaticFiles() {
  console.log('=== Web TypeScript Static File Serving Examples ===\n');

  // 1. MIME type detection
  console.log('--- 1. MIME Type Detection ---');
  console.log('HTML:', MIMETypeDetector.getByExtension('index.html'));
  console.log('JavaScript:', MIMETypeDetector.getByExtension('app.js'));
  console.log('PNG:', MIMETypeDetector.getByExtension('image.png'));

  // 2. Static file server
  console.log('\n--- 2. Static File Server ---');
  const server = new StaticFileServer('/assets');

  // Note: These would make actual HTTP requests in a real environment
  console.log('Would serve:', server.baseUrl + '/styles/main.css');
  console.log('Would serve:', server.baseUrl + '/js/app.js');

  // 3. File cache manager
  console.log('\n--- 3. File Cache Manager ---');
  const cacheManager = new FileCacheManager();

  cacheManager.set('/index.html', '<html>...</html>', 'text/html', '"abc123"');
  cacheManager.set('/app.js', 'console.log("Hello");', 'text/javascript');

  console.log('Cache stats:', cacheManager.getStats());

  const cached = cacheManager.get('/index.html');
  console.log('Cached file:', cached?.mimeType);

  // 4. Asset preloader
  console.log('\n--- 4. Asset Preloader ---');
  const preloader = new AssetPreloader();

  // Note: These would be real URLs in production
  const preloadResults = await preloader.preloadMultiple([
    '/assets/image1.png',
    '/assets/image2.png',
    '/assets/image3.png'
  ]);

  console.log('Preloaded:', preloadResults.successful);
  console.log('Failed:', preloadResults.failed);

  // 5. CDN manager
  console.log('\n--- 5. CDN Manager ---');
  const cdnManager = new CDNManager([
    'https://cdn1.example.com',
    'https://cdn2.example.com',
    'https://cdn3.example.com'
  ]);

  console.log('CDN URL:', cdnManager.getCdnUrl('/assets/app.js'));

  cdnManager.rotateCdn();
  console.log('Rotated CDN URL:', cdnManager.getCdnUrl('/assets/app.js'));

  // 6. Asset bundle manager
  console.log('\n--- 6. Asset Bundle Manager ---');
  const bundleManager = new AssetBundleManager();

  bundleManager.registerBundle('vendor', [
    '/assets/js/vendor/react.js',
    '/assets/js/vendor/lodash.js'
  ]);

  bundleManager.registerBundle('app', [
    '/assets/js/app.js',
    '/assets/js/utils.js'
  ]);

  const bundle = bundleManager.getBundle('vendor');
  console.log('Vendor bundle:', bundle);

  console.log('\n=== All Static File Serving Examples Completed ===');
}

// Export functions
export { MIMETypeDetector, StaticFileServer, FileCacheManager, AssetPreloader, CompressionHandler, CDNManager, AssetBundleManager };
export { demonstrateStaticFiles };