Learn how to work with middleware in SvelteKit applications using the Asterisms JS SDK Backend.
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.
// 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);
};// 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);
};// 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;
};
}// 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
};
};// 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
};
}// 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 });
}
};// 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;
}
};
}// 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);
}// 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);
}// 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 }
);
}
};
}// 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 });
}
};// 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();
};
}// 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 });
});
};// 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);
});
});// 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);
});
});// ✅ 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 });// ✅ 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
};