Skip to Content
Core ConceptsArchitecture

Architecture

Hoikka follows a service-oriented architecture adapted for SvelteKit’s patterns.

Request Flow

Request → hooks.server.ts (auth, cart, wishlist) +page.server.ts (thin controller: load + actions) services/*.ts (business logic + translation resolution) db/schema.ts (Drizzle ORM) +page.svelte (view)

Service Pattern

Business logic lives in singleton services under src/lib/server/services/:

class ProductService { async getById(id: number) { ... } async create(data: NewProduct) { ... } async update(id: number, data: Partial<Product>) { ... } } export const productService = new ProductService();

Route handlers are thin controllers that wire HTTP to services:

export const load = async ({ params }) => { const product = await productService.getById(id); return { product }; }; export const actions = { update: async ({ request }) => { const formData = await request.formData(); await productService.update(id, { name: formData.get("name") }); } };

Services resolve translations internally using src/lib/server/i18n.ts, returning Resolved* types with flat translated fields (e.g. product.name instead of product.translations[0].name).

Provider Pattern

Payments and shipping use a provider pattern for easy swapping:

interface PaymentProvider { code: string; createPayment(order: Order): Promise<PaymentInfo>; confirmPayment(transactionId: string): Promise<PaymentStatus>; } const PROVIDERS = new Map<string, PaymentProvider>([ ["stripe", new StripeProvider()], ["klarna", new KlarnaProvider()] ]);

Design Principles

  1. Services are singletons - One instance shared across requests
  2. Route handlers are thin - Validate input and call services
  3. Views receive typed data - Via PageData from load functions
  4. Forms use progressive enhancement - Work without JS via use:enhance
Last updated on