# Stack Research **Domain:** Multi-user gear management platform (v2.0 platform additions) **Researched:** 2026-04-03 **Confidence:** MEDIUM-HIGH This document covers ONLY the new stack additions for v2.0. The existing stack (React 19, Hono, Drizzle ORM, TanStack Router/Query, Tailwind CSS v4, Lucide React, Recharts, framer-motion, Zustand, Zod, Bun) is validated and unchanged. ## Recommended Stack ### Authentication -- Logto (Self-Hosted) | Technology | Version | Purpose | Why Recommended | |------------|---------|---------|-----------------| | Logto OSS | v1.36+ | External OIDC/OAuth 2.1 auth provider | TypeScript-native, purpose-built for app auth (not enterprise IAM), requires Postgres (shared infra), beautiful pre-built sign-in UI, React SDK with hooks, lightweight JWT validation on backend. MIT-licensed core. | | @logto/react | ^4.0.13 | React SDK for auth flows | LogtoProvider wraps app, provides useLogto() hook for sign-in/sign-out/token access. Handles OIDC redirect flow, token refresh, and user info. | | jose | ^6.2.2 | JWT validation on Hono backend | Zero-dependency, Bun-compatible, used to verify Logto-issued access tokens via JWKS. Recommended by Logto docs over heavier alternatives. | **Why Logto over alternatives:** | Provider | Why Not | |----------|---------| | Authentik | Python-based, heavyweight (designed for enterprise proxy/SSO), overkill for app-level auth. No React SDK -- requires raw OIDC integration. Better for infra-level SSO (Portainer, Grafana). | | Zitadel | Go-based, Kubernetes-first architecture, AGPL 3.0 license (copyleft since 2025). Stronger for multi-tenant B2B SaaS. Over-engineered for a single-product platform. | | SuperTokens | Session-based by default (not OIDC), requires embedding their middleware into your backend. Tighter coupling than external provider model. | | Keycloak | Java-based, heavy memory footprint (1-2GB RAM), complex admin UI. Industry standard but vastly over-scoped for this use case. | **Integration pattern:** Logto runs as a separate Docker container alongside Postgres. React app redirects to Logto's hosted sign-in page for auth flows. Hono backend validates JWT access tokens from the Authorization header using `jose` JWKS verification -- no Logto SDK needed on the backend, just standard OIDC token validation. User identity is the Logto `sub` claim (a stable string ID), stored as `userId` on all user-owned records. **Backend middleware pattern (Hono):** ```typescript import { createRemoteJWKSet, jwtVerify } from "jose"; const jwks = createRemoteJWKSet( new URL("https://logto.example.com/oidc/jwks") ); const authMiddleware = createMiddleware(async (c, next) => { const token = c.req.header("Authorization")?.replace("Bearer ", ""); if (!token) return c.json({ error: "Unauthorized" }, 401); const { payload } = await jwtVerify(token, jwks, { issuer: "https://logto.example.com/oidc", audience: "your-api-resource-indicator", }); c.set("userId", payload.sub); await next(); }); ``` **React provider pattern:** ```typescript import { LogtoProvider, LogtoConfig } from "@logto/react"; const config: LogtoConfig = { endpoint: "https://logto.example.com", appId: "", resources: ["https://api.gearbox.example.com"], }; // Wrap app root ``` ### Database -- PostgreSQL via Bun Native Driver | Technology | Version | Purpose | Why Recommended | |------------|---------|---------|-----------------| | PostgreSQL | 16+ | Primary database | Required by Logto anyway, proper concurrent access for multi-user, JSONB for flexible spec fields, full-text search for discovery feed. | | drizzle-orm | ^0.45.1 (existing) | Type-safe ORM | Already in use. Switch from `drizzle-orm/bun-sqlite` to `drizzle-orm/bun-sql` for Postgres. Schema definitions move from `sqlite-core` to `pg-core`. | | Bun native SQL | built-in | Postgres driver | Zero additional dependencies. `import { SQL } from "bun"` provides native Postgres bindings. Drizzle ORM supports it via `drizzle-orm/bun-sql`. | | postgres (postgres.js) | ^3.4.8 | Fallback Postgres driver | Only needed if Bun native SQL has issues with drizzle-kit CLI tooling (known issue #4122). More mature ecosystem, proven with Drizzle. Install as dev dependency for drizzle-kit. | **Schema migration approach:** 1. Rewrite `src/db/schema.ts` imports from `drizzle-orm/sqlite-core` to `drizzle-orm/pg-core` 2. Replace `sqliteTable` with `pgTable` 3. Replace `integer().primaryKey({ autoIncrement: true })` with `integer().primaryKey().generatedAlwaysAsIdentity()` for PKs 4. Replace `integer("created_at", { mode: "timestamp" })` with `timestamp("created_at").defaultNow().notNull()` 5. Add `userId text("user_id").notNull()` to all user-owned tables (items, threads, setups, categories) 6. Add `visibility text("visibility").notNull().default("private")` to setups and profiles 7. Generate fresh Postgres migration with `drizzle-kit generate` 8. Write a one-time data migration script (SQLite read -> Postgres insert) for existing data **drizzle.config.ts change:** ```typescript // Before { dialect: "sqlite", dbCredentials: { url: "./gearbox.db" } } // After { dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL } } ``` **Known issue:** drizzle-kit CLI does not use the Bun SQL driver for `push`/`generate` commands (GitHub issue #4122). Workaround: install `postgres` (postgres.js) as a dev dependency for drizzle-kit, while the app runtime uses Bun native SQL. ### Image Storage -- Bun Native S3 + MinIO | Technology | Version | Purpose | Why Recommended | |------------|---------|---------|-----------------| | Bun S3Client | built-in | S3 API client | Zero dependencies, native Bun bindings, extends Blob interface. Supports presigned URLs, streaming uploads. Built-in MinIO compatibility. | | MinIO | latest | Self-hosted S3-compatible object storage | Replaces local `./uploads/` directory. Single Go binary, Docker-friendly, S3 API compatible. Handles multi-user image scaling without cloud vendor lock-in. | **Why Bun native S3 over @aws-sdk/client-s3:** - Zero additional dependencies (Bun ships with it) - Simpler API (extends Blob, web-standard patterns) - Native performance bindings - Full MinIO compatibility documented by Bun team **Migration from ./uploads/:** 1. Deploy MinIO container alongside app 2. Create `gearbox-images` bucket 3. Write migration script to upload existing files from `./uploads/` to MinIO 4. Update image service to use S3Client for reads/writes 5. Serve images via presigned URLs or a proxy route on Hono **Configuration:** ```typescript import { S3Client } from "bun"; const storage = new S3Client({ accessKeyId: process.env.S3_ACCESS_KEY!, secretAccessKey: process.env.S3_SECRET_KEY!, bucket: "gearbox-images", endpoint: process.env.S3_ENDPOINT!, // e.g., http://minio:9000 }); ``` ### Supporting Libraries | Library | Version | Purpose | When to Use | |---------|---------|---------|-------------| | jose | ^6.2.2 | JWKS-based JWT verification | Every authenticated API request -- validate Logto access tokens on Hono middleware | | @logto/react | ^4.0.13 | React auth provider + hooks | Wrap app root, sign-in/sign-out flows, access token retrieval for API calls | ### Development / Infrastructure | Tool | Purpose | Notes | |------|---------|-------| | Docker Compose | Local dev environment | Postgres + Logto + MinIO containers. App still runs on bare Bun for HMR. | | drizzle-kit | Schema management | Same tool, different dialect config. `bun run db:generate` and `bun run db:push` still work. | ## Installation ```bash # New production dependencies bun add @logto/react jose # New dev dependencies (for drizzle-kit Postgres support) bun add -D postgres # No install needed for: # - Bun native S3 (built-in) # - Bun native SQL/Postgres (built-in) # - drizzle-orm (already installed, just change imports) ``` ## Alternatives Considered ### Authentication Provider | Recommended | Alternative | When to Use Alternative | |-------------|-------------|-------------------------| | Logto | Authentik | If you need proxy-mode SSO for non-OIDC apps (Portainer, legacy tools) | | Logto | Zitadel | If building multi-tenant B2B SaaS with organization-level isolation | | Logto | Keycloak | If enterprise LDAP/AD integration is mandatory | ### Database Driver | Recommended | Alternative | When to Use Alternative | |-------------|-------------|-------------------------| | Bun native SQL (`bun:sql`) | postgres.js | If Bun native SQL has concurrency bugs (known issue in Bun 1.2.0 with concurrent statements) | | Bun native SQL (`bun:sql`) | @neondatabase/serverless | If deploying to serverless/edge where persistent connections are not possible | ### Image Storage | Recommended | Alternative | When to Use Alternative | |-------------|-------------|-------------------------| | MinIO (self-hosted) | Cloudflare R2 | If you want zero-ops storage with no egress fees and don't mind cloud dependency | | MinIO (self-hosted) | Local filesystem (current) | For development/testing only. Not viable for multi-user at scale. | ## What NOT to Add | Avoid | Why | Use Instead | |-------|-----|-------------| | @aws-sdk/client-s3 | 60+ transitive dependencies, Bun has native S3 support | Bun built-in S3Client | | passport.js / express-session | Wrong paradigm -- we want external OIDC, not embedded session auth | Logto + jose JWT validation | | next-auth / auth.js | Designed for Next.js, assumes framework integration we don't have | Logto (external provider) | | better-auth | Embedded auth library, opposite of external provider model | Logto (external provider) | | pg (node-postgres) | Callback-based API, Bun has native Postgres bindings | Bun native SQL or postgres.js | | sharp / image processing libs | Premature optimization -- serve originals first, add resizing later if needed | Direct S3 storage of originals | | Redis | Not needed at this scale. Postgres handles sessions (via Logto), caching is premature | Postgres for everything | | Prisma | Already using Drizzle ORM, no reason to add a second ORM | drizzle-orm (existing) | | nanoid / cuid2 | Postgres `gen_random_uuid()` is built-in for public-facing IDs if needed | Postgres native UUID generation | | TypeORM / Sequelize | Legacy ORMs with worse TypeScript support than Drizzle | drizzle-orm (existing) | ## Infrastructure Architecture ``` Docker Compose (dev) / Docker (prod) +-- gearbox-app (Bun, port 3000) +-- gearbox-postgres (PostgreSQL 16, port 5432) | +-- gearbox DB (app data) | +-- logto DB (Logto data, separate database same instance) +-- gearbox-logto (Logto OSS, port 3001 app / 3002 admin) +-- gearbox-minio (MinIO, port 9000 API / 9001 console) ``` Logto and the app share a single Postgres instance (different databases). This keeps infrastructure simple -- one Postgres to back up, one to monitor. Logto requires PostgreSQL 14+; using 16 covers both. ## Version Compatibility | Package | Compatible With | Notes | |---------|-----------------|-------| | drizzle-orm@0.45.x | Bun native SQL | Supported via `drizzle-orm/bun-sql` driver | | drizzle-orm@0.45.x | postgres.js@3.4.x | Supported via `drizzle-orm/postgres-js` driver (fallback) | | drizzle-kit@0.31.x | PostgreSQL 16 | Generates Postgres-dialect migrations | | @logto/react@4.x | React 19 | Uses React context/hooks, compatible | | jose@6.x | Bun runtime | Explicitly lists Bun support in docs | | Logto OSS v1.36 | PostgreSQL 14+ | Logto requires PG 14 minimum; use PG 16 for both app and Logto | | Bun S3Client | MinIO latest | Documented compatibility with endpoint configuration | ## Migration Checklist (SQLite to Postgres) 1. **Schema rewrite**: `sqlite-core` -> `pg-core` imports, adjust column types 2. **Driver swap**: `drizzle-orm/bun-sqlite` -> `drizzle-orm/bun-sql` 3. **Config update**: `drizzle.config.ts` dialect and credentials 4. **Fresh migrations**: Generate from scratch for Postgres (do not try to convert SQLite migrations) 5. **Data migration**: One-time script reads SQLite, writes to Postgres 6. **Test infrastructure**: Update `createTestDb()` helper to use Postgres test database (or pg-mem for in-memory testing) 7. **CI pipeline**: Add Postgres service container for test runs 8. **Remove SQLite deps**: Remove `better-sqlite3` from devDependencies after migration confirmed ## Sources - [Logto official docs -- React quickstart](https://docs.logto.io/quick-starts/react) -- SDK setup, LogtoProvider config (HIGH confidence) - [Logto API protection -- JWT validation](https://docs.logto.io/api-protection/nodejs/express) -- jose-based middleware pattern (HIGH confidence) - [Logto OSS getting started](https://docs.logto.io/logto-oss/get-started-with-oss) -- Docker deployment, Postgres requirements (HIGH confidence) - [Logto @logto/react npm](https://www.npmjs.com/package/@logto/react) -- Version 4.0.13 confirmed (HIGH confidence) - [Drizzle ORM -- Bun SQL driver](https://orm.drizzle.team/docs/connect-bun-sql) -- Native Postgres via Bun (HIGH confidence) - [Drizzle ORM -- PostgreSQL column types](https://orm.drizzle.team/docs/column-types/pg) -- pg-core schema definitions (HIGH confidence) - [drizzle-kit Bun SQL issue #4122](https://github.com/drizzle-team/drizzle-orm/issues/4122) -- Known CLI limitation with Bun driver (MEDIUM confidence) - [Bun S3 documentation](https://bun.com/docs/runtime/s3) -- Native S3 client, MinIO config (HIGH confidence) - [MinIO GitHub](https://github.com/minio/minio) -- S3-compatible self-hosted storage (HIGH confidence) - [jose GitHub](https://github.com/panva/jose) -- JWT library v6.2.2, explicit Bun support (HIGH confidence) - [Authentik vs Zitadel comparison](https://wz-it.com/en/blog/authentik-vs-zitadel-identity-provider-comparison/) -- Auth provider analysis (MEDIUM confidence) - [Keycloak vs Authentik vs Zitadel 2026](https://blog.houseoffoss.com/post/keycloak-vs-authentik-vs-zitadel-2026-which-open-source-login-tool-should-you-use) -- Ecosystem overview (MEDIUM confidence) - [postgres.js npm](https://www.npmjs.com/package/postgres) -- Version 3.4.8, fallback driver (HIGH confidence) --- *Stack research for: GearBox v2.0 Platform Foundation* *Researched: 2026-04-03*