Files
GearBox/.planning/research/STACK.md

14 KiB

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.

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):

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:

import { LogtoProvider, LogtoConfig } from "@logto/react";

const config: LogtoConfig = {
  endpoint: "https://logto.example.com",
  appId: "<your-app-id>",
  resources: ["https://api.gearbox.example.com"],
};

// Wrap app root
<LogtoProvider config={config}>
  <App />
</LogtoProvider>

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:

// 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:

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

# 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


Stack research for: GearBox v2.0 Platform Foundation Researched: 2026-04-03