npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

studio-os

v0.3.0

Published

Self-hosted studio management: booking, class packs, memberships. Bring your own Stripe.

Readme

Studio OS

Self-hosted booking, class-pack and membership management for boutique studios — yoga, pilates, martial arts, climbing, dance, small gyms. The Mindbody escape hatch.

The structural difference no SaaS rival can copy: payments run on your own Stripe account. Studio OS never touches the money — no processing markup, no per-booking fees, no contracts. Run it in one Docker container or with plain Node on a $5 VPS or a spare machine at the studio.

  • Public schedule + booking with waiver capture — clients need no passwords (signed magic links for self-service cancellation)
  • Class packs (credits), memberships (unlimited or N/month), drop-ins
  • Atomic capacity, FIFO waitlist with auto-promotion, cancellation-window policy
  • Admin: rosters + check-in, clients, manual sales, revenue/attendance reports + CSV, one-click SQLite backup
  • Instructor logins: per-class assignments, a scoped schedule + roster portal with attendee check-in, zero admin access
  • Mindbody CSV importer (clients + pricing options), idempotent and dry-runnable
  • Stripe Checkout for packs and membership subscriptions (optional), SMTP email (optional — without it, emails land in data/outbox/ and links show on screen)
  • Everything server-rendered (EJS + HTMX + Pico.css, all vendored — no CDN, no build step, works offline), SQLite in one file, PWA manifest + service worker

5-minute quickstart

Docker

git clone <this repo> studio-os && cd studio-os
docker compose up -d
# open http://localhost:3000 → setup wizard creates your studio + owner login

The database persists in the studio-data volume. Configure SMTP/Stripe by uncommenting the environment: block in docker-compose.yml.

Verified end-to-end (Docker Engine 28.x, linux/amd64, node:22-slim base — better-sqlite3 uses its prebuilt glibc binary, no compiler stage needed):

docker compose build
docker compose up -d
curl -i http://localhost:3000/   # 302 → /setup on a fresh volume
# setup wizard → admin one-off class → public schedule → guest booking: all OK
docker compose down -v           # stop + remove the data volume

Bare Node (Node 22+)

git clone <this repo> studio-os && cd studio-os
npm install
npm start
# open http://localhost:3000 → setup wizard

The database is a single file at data/studio.db (override with DB_PATH). Copy .env.example to .env to configure port, SMTP, Stripe. Nothing is required — with zero config the app runs fully offline.

Want to look around with data first?

npm run seed   # demo studio: classes, weekly schedule, clients, passes
npm start      # admin login: [email protected] / studio-demo

First-run walkthrough

  1. Setup wizard: studio name, timezone, currency, owner account.
  2. Admin → Class types: create your classes (duration, capacity, drop-in price, credits).
  3. Admin → Schedule: add weekly rules — instances materialize on a rolling 8-week horizon automatically (at boot, daily, and on rule changes).
  4. Share the public schedule (/). Clients book with just name + email and sign the waiver on first booking.
  5. Sell packs/memberships from a client's profile (manual), or connect Stripe for online purchase.
  6. Optional: Admin → Instructors → "Add instructor login", then tick the classes they teach. Instructors sign in on the same staff login page and get a portal with just their schedule and rosters (with check-in) — no admin access.

Migrating from Mindbody

You need two CSV exports from Mindbody:

  1. Clients — Reports → Clients (or Clients → Export). Standard columns: First Name, Last Name, Email, Mobile Phone, Notes, Liability Waiver, ...
  2. Pricing options (remaining passes) — Reports → Pricing Options / "Remaining sessions". Standard columns: Client, Email, Pricing Option, Remaining, Total, Expiration, Price Paid, ...

Then either paste the CSVs into Admin → Import (dry-run checkbox included), or run the CLI:

# preview first — prints every row's action, writes nothing
node scripts/import-mindbody.mjs --clients clients.csv --passes pricing.csv --dry-run

# real import
node scripts/import-mindbody.mjs --clients clients.csv --passes pricing.csv

Behavior:

  • Idempotent by email — re-running updates existing clients/passes, never duplicates. Safe to re-export from Mindbody and re-import on cutover day.
  • Rows without a valid email are skipped and reported (Mindbody allows email-less clients; Studio OS keys everything on email).
  • Passes for emails not in the clients file auto-create a bare client record.
  • A signed "Liability Waiver = Yes" column marks the waiver as signed so clients aren't re-prompted.
  • Non-standard column names? Pass --mapping mapping.json:
{
  "clients": { "email": ["E-Mail-Adresse"], "firstName": ["Vorname"] },
  "passes": { "remaining": ["Sessions Left"] }
}

Keys you provide override the defaults; everything else keeps sane defaults (see DEFAULT_MAPPING in src/services/importer.js). Dates accept M/D/YYYY and ISO; money accepts $1,500.00-style strings.

Stripe setup (optional, bring-your-own account)

Without Stripe, every purchase flow falls back to "pay at studio / FPS" and you activate purchases manually from the client profile — fully usable cash-only.

  1. Create a Stripe account (test mode is fine to start).
  2. Set env vars (.env or compose environment):
    • STRIPE_SECRET_KEY — Developers → API keys
    • STRIPE_PUBLISHABLE_KEY
    • STRIPE_WEBHOOK_SECRET — see step 3
  3. Add a webhook endpoint in Stripe: https://your-domain/webhooks/stripe, events checkout.session.completed and customer.subscription.deleted. Copy the signing secret into STRIPE_WEBHOOK_SECRET. (Local testing: stripe listen --forward-to localhost:3000/webhooks/stripe.) The webhook secret is not optional: online payment stays off until it is set, because the webhook is what turns a payment into a pass. Admin → Settings tells you which half is missing.
  4. Class packs sell immediately via Checkout (price taken from the product; optionally paste a Stripe Price ID on the product for Stripe-side pricing).
  5. Memberships are Stripe subscriptions: create a recurring Price in Stripe, paste its price_... id into the plan in Admin → Products.

Fulfillment is idempotent (keyed on the Checkout session id) — Stripe's webhook retries can't double-credit. Card data never touches your server; clients pay on Stripe-hosted Checkout. Set BASE_URL so success/cancel redirects and emailed links use your public domain.

Email (optional)

Set SMTP_HOST (+ SMTP_PORT/SMTP_USER/SMTP_PASS/SMTP_FROM) to send booking confirmations, cancellation notices, waitlist promotions and magic links. Without SMTP, every email is written to data/outbox/ as a .eml file and magic links are shown directly in the UI — everything remains testable.

Security

What's protected out of the box:

  • CSRF: every state-changing form carries a session-bound token; POSTs without a valid token get a 403. The Stripe webhook is exempt — it is authenticated by Stripe's signature over the raw body instead.
  • Rate limiting (in-memory fixed window, per IP + route): magic-link requests 5/15 min, admin login 10/15 min, public booking/buy POSTs 30/15 min. Over the limit → friendly 429. Behind a reverse proxy, set TRUST_PROXY=1 so limits key on the first X-Forwarded-For hop; without it that header is ignored (it's spoofable).
  • Reverse-proxy awareness: TRUST_PROXY=1 also makes the app honour X-Forwarded-Proto. TLS usually terminates at the proxy, so without it every emailed magic link is built as http:// — putting a 7-day auth token on the wire in plaintext — and the session cookie never gets its Secure flag. Set it, or pin BASE_URL to your https origin.
  • Passwords are bcrypt-hashed; client self-service uses expiring HMAC-signed magic links (no client passwords); sessions are signed SameSite=Lax HttpOnly cookies (Secure too, over https); webhook fulfillment is idempotent. The staff login does the same bcrypt work whether or not the email exists, so it can't be used to enumerate accounts.

What's not there yet — plan accordingly:

  • No 2FA on staff logins.
  • The rate limiter is single-instance and in-memory: counters are per-process and reset on restart. Fine for the one-container target; a multi-instance deployment needs a shared store (or limit at the proxy).
  • HTTPS is your reverse proxy's job — run behind Caddy/Traefik/nginx.

Backups

Admin → Settings → Download backup produces a consistent snapshot via SQLite VACUUM INTO. Or just copy data/studio.db while the app is stopped. It's one file — cron it anywhere.

Screenshots

Public booking site — what your clients see. No account needed; the waiver is collected on first booking, and returning clients are matched by email so membership or pack credits apply automatically.

| Schedule | Booking a class | |---|---| | Public schedule | Booking form |

Buy page — class packs and memberships, on your Stripe account (or pay-at-studio if you haven't connected Stripe).

Buy page

Admin — dashboard, schedule, class roster with one-tap check-in, client profiles, and revenue reports.

| Dashboard | Roster & check-in | |---|---| | Admin dashboard | Roster |

| Schedule management | Client profile | Reports | |---|---|---| | Schedule | Client | Reports |

Development

npm install
npm test        # node:test + supertest, no network
npm run dev     # --watch mode

Stack: Node 22+, Express, better-sqlite3 (WAL), EJS + HTMX + Pico.css (vendored). No frontend build. Tests cover the booking engine (capacity races, waitlist FIFO, credit deduct/refund, late-cancel policy), schedule materialization across timezones, the web flows, importer idempotency, and Stripe webhook fulfillment against a mock client.

Honest v0.1 limitations

  • Single studio, single location, one timezone.
  • See Security for what is and isn't covered (no 2FA; single-instance in-memory rate limiter; HTTPS via your reverse proxy).
  • Membership renewal bookkeeping is driven by Stripe webhooks; cash memberships need manual renewal (mark paid each cycle).
  • Monthly-credit memberships reset on a simple cycle from cycle_started_on; no proration.
  • Emails are plain text; no branded HTML templates yet.
  • English UI only (currency is a setting — no hardcoded symbols).
  • Reports are month-granularity tables + CSV; no charts.

Roadmap

  • Multi-location support
  • Branded/installable PWA per studio (custom colors, icon, name)
  • SMS reminders (Twilio-compatible, bring-your-own account)
  • Recurring cash membership invoicing + renewal reminders
  • Retail/POS, gift cards, video — explicitly out of scope for now, as are staff payroll, per-instructor payouts, marketing automation, native apps, GDPR export tooling and i18n.

License

MIT