# 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: "<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:**

```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*
