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
- Services are singletons - One instance shared across requests
- Route handlers are thin - Validate input and call services
- Views receive typed data - Via
PageDatafrom load functions - Forms use progressive enhancement - Work without JS via
use:enhance
Last updated on