Iurii RoguliaIurii Rogulia
AboutServicesPricingProjectsSkillsStackReviewsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, senior full-stack software engineer. Professionally building software since 2001.

Think of a number
PricingQuality checklistPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland 🇫🇮

Back to projects

Iurii Books: kri.rocks – Booking Website and Online Scheduling SaaS for Beauty Businesses

September 28, 2026

Vertical SaaS that gives a salon, barbershop or solo beauty specialist their own booking website in minutes. Services pre-filled for 13 business types, four designs, 32 languages, one monthly subscription and zero commission on bookings.

Live Demo

Stack

Next.jsReactTypeScriptNode.js

Libraries

HonoDrizzle ORMBetter Authnext-intlTailwind CSSpdf-libZodioredis

Databases

PostgreSQLRedis

Services

MollieResendCloudflareSentryvatnode API

Topics

SaaSMulti-TenancyOnline BookingBillingTax/VATi18nMonorepoTurborepo

Key Results

  • A salon goes from sign-up to a shareable booking website in four steps – business type, name and city, services, team
  • Clients only ever see times when a specialist is working and free, so two clients booking online can’t take the same slot
  • Each salon’s website and booking emails work in any of 32 languages – adding a language means adding JSON files, no code
  • One subscription that grows only with the team, priced per country from a single validated file – no commission, no per-booking fees
  • A double charge is prevented by the database itself, not by application code remembering to check

The Problem

Small beauty businesses – a two-chair nail studio, a solo brow artist, a barbershop with three people – still take most of their bookings in Instagram DMs and WhatsApp. ‘Anything free tomorrow at 10?’, ‘How much is a lash lift?’, ‘Can I move to 5pm?’ – every one of those is answered by hand, often between clients or late at night.

The existing options don’t fit this owner well. Marketplaces take a commission on every booking and put the salon’s page next to its competitors. Website builders give a blank canvas and expect the owner to design, write and wire up booking themselves. Enterprise salon software is priced and built for chains.

The Solution

KRI is a vertical SaaS I designed and built end to end: product, architecture, backend, frontend, billing and infrastructure. An owner picks a business type, enters a name and city, checks a pre-filled list of services and prices, and adds their team with working hours. At the end of those four steps the salon has its own booking website at a personal kri.rocks address.

The product is opinionated on purpose. Instead of a builder, the owner chooses one of 13 business types – beauty salon, hair salon, barbershop, nail studio, lashes and brows, massage, spa, tattoo and more – and one of four designs (Minimal, Modern, Classic, Soft). Typical services arrive filled in, so the first thing an owner does is adjust prices, not build pages.

Clients book around the clock and see only real availability: each specialist has their own hours and days off, and the client can choose who they book with. A confirmation goes out on booking, a reminder before the visit and a notice if it is cancelled – each in the language the client booked in. The owner runs everything from one dashboard – today’s bookings, the calendar, clients with visit history, services, team, the website itself and billing – designed for the phone between appointments first.

Pricing is one monthly subscription that grows only with the number of specialists taking bookings, with no commission and no setup fee. The price depends on the country: every country selectable at sign-up has its national currency and either its own price or a floor price, and the whole table lives in one file that is validated on every type-check – a price below the floor, a missing currency or an unknown field fails the type-check instead of reaching a customer.

Results

MetricValue
ArchitectureNext.js 16 web app + separate Hono API, deployed as two services
Codebase~53k lines of TypeScript in a Turborepo monorepo
Languages32, per-audience address register checked by a locale linter
Business types / designs13 / 4
Countries at sign-up226, each with its currency and price validated on type-check
Sign-inPasswordless – magic link plus a 6-digit code
Commission on bookings0%
Double-booking protectionPer-specialist advisory lock + overlap check in one transaction
Double-charge protectionPartial unique index: at most one live payment per invoice

Under the Hood

For the technically curious, here is how the core pieces are built.

Split Architecture and Host-Based Tenancy

The web app and the API are separate applications and deploy as separate containers – a deliberate decision so business logic can never leak into Next.js route handlers. The web app resolves every request by host into a zone: the marketing site, the app subdomain with the dashboard and onboarding, or a salon’s public page, which is rewritten to an internal route with the tenant slug attached. Public tenant lookups read a Redis cache first and fall back to PostgreSQL, so a salon’s page doesn’t cost a database round-trip on every visit. Authenticated calls go through the web app’s own origin, so the auth cookie stays host-only.

Booking That Can’t Double-Book

A booking is created in one transaction that takes a Postgres advisory lock scoped to the specialist, re-reads their availability and checks for an overlapping booking before inserting. Two clients racing for the same slot are serialized; the second one gets a clean ‘this time was just taken’, never a duplicate:

const booking = await db.transaction(async (tx) => {
  // Serialize concurrent bookings for the same specialist: availability check,
  // overlap check and insert are atomic against another booking for them.
  await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${"booking:" + masterId}))`);
 
  const [live] = await tx
    .select({ isActive: masters.isActive, lastWorkingDay: masters.lastWorkingDay })
    .from(masters)
    .where(eq(masters.id, masterId))
    .limit(1);
  if (!live || !isMasterBookableOn(live, localDate)) {
    throw new BookingRejected(409, "not_working", "This time is not available");
  }
  // ...check the requested time falls inside the specialist's working hours
 
  const [conflict] = await tx
    .select({ id: bookings.id })
    .from(bookings)
    .where(
      and(
        eq(bookings.masterId, masterId),
        lt(bookings.startsAt, endsAt),
        gt(bookings.endsAt, startsAt),
        LIVE_BOOKING
      )
    )
    .limit(1);
  if (conflict) throw new BookingRejected(409, "slot_taken", "This time was just taken");
  // ...insert the booking
});

There is deliberately no unique constraint on (specialist, start time): an owner may put two clients on one specialist from the dashboard. Only client-facing booking is serialized. All times are stored in UTC and shown in the salon’s time zone, and slot generation skips wall-clock times that a daylight-saving change removes.

Billing Where the Database Holds the Line

Subscription billing runs on Mollie, but without Mollie’s Subscriptions API: the amount changes every period with the number of specialists and the VAT treatment, so KRI stores the card mandate and issues each charge itself. The invariants that money depends on are enforced by PostgreSQL, not by code remembering to check:

// The single most important guard against a double charge.
uniqueIndex("payment_attempts_one_live_idx")
  .on(t.documentId)
  .where(sql`${t.state} IN ('created', 'pending', 'succeeded', 'review')`),

A second partial unique index allows only one live invoice per tenant and period. Issuing an invoice, charging it, cancelling a subscription and detaching a card all take the same per-tenant advisory lock around their ‘is anything owed?’ check, so a cancellation and a renewal can never each act on facts the other has just changed. Race cases like these are covered by tests that run against a real PostgreSQL with a controlled interleaving.

Every invoice and credit note is a numbered, frozen snapshot rendered to PDF with pdf-lib. EU business customers’ VAT numbers are checked against VIES through the vatnode API, with three outcomes kept apart – valid, invalid and ‘register unreachable’ – because treating an outage as invalid charges VAT wrongly and treating it as valid drops VAT that is owed. Provider notifications are treated as hints: the handler reads the real state back from Mollie, and every write is conditional on the state it expects, so a notification delivered twice changes nothing.

32 Languages, Two Audiences

Translations are plain JSON per locale, and a language is available as soon as its folder is complete – adding one means adding files, not code. A locale linter runs with the regular lint and checks key order against English, reports key drift between locales for a human decision, and enforces terminology and address register per language. Register is a property of the audience, not the language: KRI writes to the salon owner and, on the owner’s behalf, to the salon’s clients. German, for example, addresses the owner as du and the client as Sie, so the linter maps each namespace to its audience and checks the right form in each.

Iurii RoguliaAvailable

Need something similar?

I build custom solutions – from APIs to full products. Let’s talk about your project.

View all projects

Related projects

vatnode.dev – EU VAT Validation API
vatnode.dev – EU VAT Validation API
January 19, 2026
vatnode.dev – EU VAT Validation API

Developer-first SaaS API for EU VAT validation via VIES with Redis caching, change monitoring, and webhook notifications.

Stack

Next.jsReactTypeScriptHonoNode.jsTurborepoDockerCaddy

Libraries

Drizzle ORMBullMQZodBetter AuthTanStack QueryZustand

Databases

PostgreSQLRedis

Services

StripeResendGoogle Analytics

Topics

SaaSAPIAuthFintechTax/VATMonorepoWebhooksSEOSchema.org
HTPBE.TECH – Has This PDF Been Edited?
HTPBE.TECH – Has This PDF Been Edited?
September 25, 2024
HTPBE.TECH – Has This PDF Been Edited?

SaaS platform for PDF authenticity verification with a public REST API.

Stack

Next.jsReactTypeScript

Libraries

Drizzle ORMZodNextAuth.jspdf-lib

Databases

PostgreSQL

Services

MollieResendGoogle Analytics

Topics

SaaSPDFAPIAuthSecuritySEOSchema.org

Related posts

Turborepo Monorepo: Next.js and Hono in One Repo With Shared Types
August 4, 2025· 13 min
Turborepo Monorepo: Next.js and Hono in One Repo With Shared Types

Turborepo monorepo with Next.js and Hono, sharing one set of TypeScript types across frontend and backend.

Stack

Next.jsTypeScriptHonoTurborepoNode.jsDocker

Libraries

Drizzle ORMZod

Databases

PostgreSQLRedis

Services

Vercel

Topics

ArchitectureMonorepoSaaS
Next.js SaaS Checklist: Launch Production-Ready in 8 Weeks
January 19, 2026· 17 min
Next.js SaaS Checklist: Launch Production-Ready in 8 Weeks

40+ point production SaaS checklist: auth, Stripe billing, PostgreSQL, rate limiting, email, monitoring, and security – with honest 8-week build estimates.

Stack

Next.jsTypeScript

Libraries

Drizzle ORMBetter AuthBullMQZodReact Email

Databases

PostgreSQLRedis

Services

StripeVercelResendSentry

Topics

SaaSArchitectureAuth