Middleware

Learn how to work with middleware in SvelteKit applications using the Asterisms JS SDK Backend.

Overview

Middleware in SvelteKit is implemented through hooks, which allow you to intercept and modify requests and responses. The Asterisms JS SDK Backend provides utilities to integrate seamlessly with SvelteKit's hooks system.

Basic Middleware Setup

Setting up SDK in hooks.server.ts

// src/hooks.server.ts
import { createSvelteKitBackendSDK } from '@asterisms/sdk-backend';
import { createFetchAdapter } from '@asterisms/sdk';
import { building } from '$app/environment';
import type { Handle } from '@sveltejs/kit';

let provider: AsterismsSDKProvider | null = null;

export const handle: Handle = async ({ event, resolve }) => {
  // Initialize SDK if not already done
  if (!provider && !building) {
    provider = createSvelteKitBackendSDK({
      env: process.env,
      building,
      httpFactory: createFetchAdapter(fetch),
      registrationData: {
        bundleId: 'com.example.myapp',
        capabilities: ['FRONTEND_WEBSITE'],
        description: 'My SvelteKit Application',
        name: 'MyApp',
        subdomain: 'myapp',
        title: 'My App'
      }
    });

    await provider.bootSDK();
  }

  // Make SDK available to all routes
  if (provider) {
    event.locals.sdk = provider.sdk;
  }

  // Continue with the request
  return await resolve(event);
};

Authentication Middleware

// src/hooks.server.ts
import { redirect } from '@sveltejs/kit';
import { createSvelteKitBackendSDK } from '@asterisms/sdk-backend';
import { createFetchAdapter } from '@asterisms/sdk';
import { building } from '$app/environment';
import type { Handle } from '@sveltejs/kit';

let provider: AsterismsSDKProvider | null = null;

export const handle: Handle = async ({ event, resolve }) => {
  // Initialize SDK if not already done
  if (!provider && !building) {
    provider = createSvelteKitBackendSDK({
      env: process.env,
      building,
      httpFactory: createFetchAdapter(fetch),
      registrationData: {
        bundleId: 'com.example.myapp',
        capabilities: ['FRONTEND_WEBSITE'],
        description: 'My SvelteKit Application',
        name: 'MyApp',
        subdomain: 'myapp',
        title: 'My App'
      }
    });

    await provider.bootSDK();
  }

  if (provider) {
    event.locals.sdk = provider.sdk;
  }

  // Check authentication for protected routes
  if (
    event.url.pathname.startsWith('/dashboard') ||
    event.url.pathname.startsWith('/api/protected')
  ) {
    const sdk = event.locals.sdk;

    if (!sdk) {
      throw new Error('SDK not available');
    }

    const auth = sdk.authorization();

    // Extract token from cookies or headers
    const token =
      event.cookies.get('auth_token') ||
      event.request.headers.get('Authorization')?.replace('Bearer ', '');

    if (!token) {
      if (event.url.pathname.startsWith('/api/')) {
        return new Response(JSON.stringify({ error: 'Unauthorized' }), {
          status: 401,
          headers: { 'Content-Type': 'application/json' }
        });
      }
      throw redirect(302, '/login');
    }

    try {
      // Note: Check actual authorization service methods for token validation
      // This is a placeholder - verify actual method exists
      const user = await auth.validateToken(token);
      event.locals.user = user;
    } catch (error) {
      console.error('Token validation failed:', error);

      if (event.url.pathname.startsWith('/api/')) {
        return new Response(JSON.stringify({ error: 'Invalid token' }), {
          status: 401,
          headers: { 'Content-Type': 'application/json' }
        });
      }
      throw redirect(302, '/login');
    }
  }

  return await resolve(event);
};

Permission-Based Middleware

Role-Based Access Control

// src/lib/middleware/rbac.ts
import type { RequestEvent } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';

export function createRBACMiddleware(requiredPermissions: string[]) {
  return async (event: RequestEvent) => {
    const sdk = event.locals.sdk;
    const user = event.locals.user;

    if (!sdk || !user) {
      throw error(401, 'Unauthorized');
    }

    const auth = sdk.authorization();

    // Check if user has required permissions
    for (const permission of requiredPermissions) {
      const hasPermission = await auth.hasPermission(permission);
      if (!hasPermission) {
        throw error(403, `Missing permission: ${permission}`);
      }
    }

    return true;
  };
}

Using Permission Middleware in Routes

// src/routes/admin/users/+page.server.ts
import { createRBACMiddleware } from '$lib/middleware/rbac';
import type { PageServerLoad } from './$types';

const requireAdminPermissions = createRBACMiddleware(['admin.users.read', 'admin.system.access']);

export const load: PageServerLoad = async (event) => {
  // Check permissions
  await requireAdminPermissions(event);

  const sdk = event.locals.sdk;
  const storage = sdk.storage();

  // Load users data
  const users = await storage.list('users');

  return {
    users
  };
};

API Route Middleware

Generic API Middleware

// src/lib/middleware/api.ts
import type { RequestEvent } from '@sveltejs/kit';
import { json } from '@sveltejs/kit';

export interface APIMiddlewareOptions {
  requireAuth?: boolean;
  permissions?: string[];
  rateLimit?: {
    windowMs: number;
    max: number;
  };
}

export function createAPIMiddleware(options: APIMiddlewareOptions = {}) {
  return async (event: RequestEvent) => {
    const sdk = event.locals.sdk;

    if (!sdk) {
      return json({ error: 'Service unavailable' }, { status: 503 });
    }

    // Authentication check
    if (options.requireAuth) {
      const user = event.locals.user;
      if (!user) {
        return json({ error: 'Unauthorized' }, { status: 401 });
      }
    }

    // Permission check
    if (options.permissions && options.permissions.length > 0) {
      const auth = sdk.authorization();

      for (const permission of options.permissions) {
        const hasPermission = await auth.hasPermission(permission);
        if (!hasPermission) {
          return json(
            {
              error: `Missing permission: ${permission}`
            },
            { status: 403 }
          );
        }
      }
    }

    // Rate limiting (basic implementation)
    if (options.rateLimit) {
      const clientId = event.getClientAddress();
      const rateLimitKey = `rate_limit_${clientId}`;

      // This is a simplified example - use Redis or similar in production
      const storage = sdk.storage();
      const requests = (await storage.get(rateLimitKey)) || [];
      const now = Date.now();
      const windowStart = now - options.rateLimit.windowMs;

      // Filter out old requests
      const recentRequests = requests.filter((timestamp: number) => timestamp > windowStart);

      if (recentRequests.length >= options.rateLimit.max) {
        return json(
          {
            error: 'Rate limit exceeded'
          },
          { status: 429 }
        );
      }

      // Add current request
      recentRequests.push(now);
      await storage.set(rateLimitKey, recentRequests);
    }

    return null; // Continue processing
  };
}

Using API Middleware

// src/routes/api/users/+server.ts
import { json } from '@sveltejs/kit';
import { createAPIMiddleware } from '$lib/middleware/api';
import type { RequestHandler } from './$types';

const apiMiddleware = createAPIMiddleware({
  requireAuth: true,
  permissions: ['users.read'],
  rateLimit: {
    windowMs: 60000, // 1 minute
    max: 100 // 100 requests per minute
  }
});

export const GET: RequestHandler = async (event) => {
  // Run middleware
  const middlewareResult = await apiMiddleware(event);
  if (middlewareResult) {
    return middlewareResult; // Middleware returned an error response
  }

  const sdk = event.locals.sdk;
  const storage = sdk.storage();

  try {
    const users = await storage.list('users');
    return json({ users });
  } catch (error) {
    console.error('Failed to fetch users:', error);
    return json({ error: 'Failed to fetch users' }, { status: 500 });
  }
};

Logging and Monitoring Middleware

Request Logging

// src/lib/middleware/logging.ts
import type { RequestEvent } from '@sveltejs/kit';

export function createLoggingMiddleware() {
  return async (event: RequestEvent, next: () => Promise<Response>) => {
    const start = Date.now();
    const { method, url } = event.request;
    const userAgent = event.request.headers.get('User-Agent') || 'Unknown';
    const clientIP = event.getClientAddress();

    console.log(`[${new Date().toISOString()}] ${method} ${url} - ${clientIP}`);

    try {
      const response = await next();
      const duration = Date.now() - start;

      console.log(
        `[${new Date().toISOString()}] ${method} ${url} - ${response.status} - ${duration}ms`
      );

      return response;
    } catch (error) {
      const duration = Date.now() - start;
      console.error(
        `[${new Date().toISOString()}] ${method} ${url} - ERROR - ${duration}ms`,
        error
      );
      throw error;
    }
  };
}

Performance Monitoring

// src/lib/middleware/monitoring.ts
import type { RequestEvent } from '@sveltejs/kit';

interface PerformanceMetrics {
  method: string;
  url: string;
  duration: number;
  status: number;
  timestamp: number;
  userAgent?: string;
  clientIP?: string;
}

export function createMonitoringMiddleware() {
  return async (event: RequestEvent, next: () => Promise<Response>) => {
    const start = performance.now();
    const { method, url } = event.request;

    try {
      const response = await next();
      const duration = performance.now() - start;

      // Collect metrics
      const metrics: PerformanceMetrics = {
        method,
        url,
        duration,
        status: response.status,
        timestamp: Date.now(),
        userAgent: event.request.headers.get('User-Agent') || undefined,
        clientIP: event.getClientAddress()
      };

      // Send to monitoring service
      await sendMetrics(metrics);

      // Add performance headers
      response.headers.set('X-Response-Time', `${duration.toFixed(2)}ms`);

      return response;
    } catch (error) {
      const duration = performance.now() - start;

      // Log error metrics
      const metrics: PerformanceMetrics = {
        method,
        url,
        duration,
        status: 500,
        timestamp: Date.now(),
        userAgent: event.request.headers.get('User-Agent') || undefined,
        clientIP: event.getClientAddress()
      };

      await sendMetrics(metrics);
      throw error;
    }
  };
}

async function sendMetrics(metrics: PerformanceMetrics) {
  // Implementation depends on your monitoring service
  // Examples: DataDog, New Relic, custom analytics
  console.log('Metrics:', metrics);
}

Error Handling Middleware

Global Error Handler

// src/lib/middleware/error-handler.ts
import { json } from '@sveltejs/kit';
import type { RequestEvent } from '@sveltejs/kit';

export function createErrorHandlerMiddleware() {
  return async (event: RequestEvent, next: () => Promise<Response>) => {
    try {
      return await next();
    } catch (error) {
      console.error('Unhandled error:', error);

      // Log error to monitoring service
      await logError(error, event);

      // Return appropriate error response
      if (event.url.pathname.startsWith('/api/')) {
        return json(
          {
            error: 'Internal server error',
            timestamp: new Date().toISOString(),
            path: event.url.pathname
          },
          { status: 500 }
        );
      }

      // For non-API routes, re-throw to let SvelteKit handle it
      throw error;
    }
  };
}

async function logError(error: any, event: RequestEvent) {
  const errorInfo = {
    message: error.message,
    stack: error.stack,
    url: event.url.toString(),
    method: event.request.method,
    userAgent: event.request.headers.get('User-Agent'),
    clientIP: event.getClientAddress(),
    timestamp: new Date().toISOString(),
    user: event.locals.user?.id || 'anonymous'
  };

  // Send to error tracking service (Sentry, etc.)
  console.error('Error logged:', errorInfo);
}

Validation Middleware

Request Validation

// src/lib/middleware/validation.ts
import { json } from '@sveltejs/kit';
import type { RequestEvent } from '@sveltejs/kit';
import { z } from 'zod';

export function createValidationMiddleware<T>(schema: z.ZodSchema<T>) {
  return async (event: RequestEvent): Promise<T | Response> => {
    try {
      const body = await event.request.json();
      const validatedData = schema.parse(body);
      return validatedData;
    } catch (error) {
      if (error instanceof z.ZodError) {
        return json(
          {
            error: 'Validation failed',
            details: error.errors
          },
          { status: 400 }
        );
      }

      return json(
        {
          error: 'Invalid request body'
        },
        { status: 400 }
      );
    }
  };
}

Using Validation Middleware

// src/routes/api/users/+server.ts
import { json } from '@sveltejs/kit';
import { createValidationMiddleware } from '$lib/middleware/validation';
import { z } from 'zod';
import type { RequestHandler } from './$types';

const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1),
  role: z.enum(['admin', 'user', 'viewer'])
});

const validateCreateUser = createValidationMiddleware(createUserSchema);

export const POST: RequestHandler = async (event) => {
  // Validate request body
  const validationResult = await validateCreateUser(event);
  if (validationResult instanceof Response) {
    return validationResult; // Validation failed
  }

  const userData = validationResult;
  const sdk = event.locals.sdk;
  const storage = sdk.storage();

  try {
    const newUser = await storage.create('users', userData);
    return json({ user: newUser }, { status: 201 });
  } catch (error) {
    console.error('Failed to create user:', error);
    return json({ error: 'Failed to create user' }, { status: 500 });
  }
};

Combining Multiple Middleware

Middleware Chain

// src/lib/middleware/chain.ts
import type { RequestEvent } from '@sveltejs/kit';

type MiddlewareFunction = (event: RequestEvent, next: () => Promise<Response>) => Promise<Response>;

export function createMiddlewareChain(...middlewares: MiddlewareFunction[]) {
  return async (event: RequestEvent, finalHandler: () => Promise<Response>) => {
    let index = 0;

    const next = async (): Promise<Response> => {
      if (index >= middlewares.length) {
        return await finalHandler();
      }

      const middleware = middlewares[index++];
      return await middleware(event, next);
    };

    return await next();
  };
}

Complete API Route with Middleware Chain

// src/routes/api/protected/data/+server.ts
import { json } from '@sveltejs/kit';
import { createMiddlewareChain } from '$lib/middleware/chain';
import { createLoggingMiddleware } from '$lib/middleware/logging';
import { createMonitoringMiddleware } from '$lib/middleware/monitoring';
import { createErrorHandlerMiddleware } from '$lib/middleware/error-handler';
import { createAPIMiddleware } from '$lib/middleware/api';
import type { RequestHandler } from './$types';

// Create middleware chain
const middlewareChain = createMiddlewareChain(
  createLoggingMiddleware(),
  createMonitoringMiddleware(),
  createErrorHandlerMiddleware()
);

const apiMiddleware = createAPIMiddleware({
  requireAuth: true,
  permissions: ['data.read'],
  rateLimit: {
    windowMs: 60000,
    max: 50
  }
});

export const GET: RequestHandler = async (event) => {
  return await middlewareChain(event, async () => {
    // Run API middleware
    const middlewareResult = await apiMiddleware(event);
    if (middlewareResult) {
      return middlewareResult;
    }

    // Main route logic
    const sdk = event.locals.sdk;
    const storage = sdk.storage();

    const data = await storage.list('protected_data');
    return json({ data });
  });
};

Testing Middleware

Unit Tests

// src/lib/middleware/api.test.ts
import { describe, it, expect, vi } from 'vitest';
import { createAPIMiddleware } from './api';

describe('API Middleware', () => {
  it('should allow access with valid permissions', async () => {
    const mockEvent = {
      locals: {
        sdk: {
          authorization: () => ({
            hasPermission: vi.fn().mockResolvedValue(true)
          })
        },
        user: { id: 'user123' }
      }
    };

    const middleware = createAPIMiddleware({
      requireAuth: true,
      permissions: ['test.read']
    });

    const result = await middleware(mockEvent as any);
    expect(result).toBeNull(); // Should continue processing
  });

  it('should deny access without permissions', async () => {
    const mockEvent = {
      locals: {
        sdk: {
          authorization: () => ({
            hasPermission: vi.fn().mockResolvedValue(false)
          })
        },
        user: { id: 'user123' }
      }
    };

    const middleware = createAPIMiddleware({
      requireAuth: true,
      permissions: ['test.read']
    });

    const result = await middleware(mockEvent as any);
    expect(result).toBeDefined();
    expect(result?.status).toBe(403);
  });
});

Integration Tests

// src/routes/api/test-route/+server.test.ts
import { describe, it, expect } from 'vitest';
import { GET } from './+server';

describe('/api/test-route', () => {
  it('should require authentication', async () => {
    const mockEvent = {
      locals: {
        sdk: {
          /* mock SDK */
        },
        user: null // No user
      },
      request: new Request('http://localhost/api/test-route')
      // ... other mock properties
    };

    const response = await GET(mockEvent as any);
    expect(response.status).toBe(401);
  });
});

Best Practices

Security Considerations

// ✅ Good: Always validate input
const middleware = createValidationMiddleware(schema);

// ✅ Good: Use rate limiting
const apiMiddleware = createAPIMiddleware({
  rateLimit: { windowMs: 60000, max: 100 }
});

// ✅ Good: Log security events
console.log('Security: Unauthorized access attempt', {
  ip: event.getClientAddress(),
  path: event.url.pathname
});

// ❌ Bad: Exposing sensitive information
return json({ error: error.stack }, { status: 500 });

Performance Optimization

// ✅ Good: Cache expensive operations
const permissionCache = new Map();

async function hasPermissionCached(permission: string): Promise<boolean> {
  if (permissionCache.has(permission)) {
    return permissionCache.get(permission);
  }

  const result = await auth.hasPermission(permission);
  permissionCache.set(permission, result);
  return result;
}

// ✅ Good: Use appropriate rate limits
const rateLimit = {
  windowMs: 60000,
  max: process.env.NODE_ENV === 'production' ? 100 : 1000
};

Next Steps