Development guide for the Localhost-Tunnel monorepo - a production-ready localhost tunneling service with HTTP/TCP support, team collaboration, and multi-provider payments
A comprehensive guide for working with the Localhost-Tunnel codebase - a production-ready localhost tunneling service similar to ngrok/localtunnel.
This is a **Turborepo monorepo** with three main packages:
```bash
npm install # Install all dependencies (monorepo)
npm run db:generate # Generate Prisma client (required before first run)
npm run db:push # Push database schema to SQLite
npm run dev # Start all apps in development mode (turbo)
```
```bash
npm run test:unit # Unit tests (Vitest)
npm run test:integration # Integration tests
npm run test:e2e # E2E tests (Playwright)
npm run test:coverage # All tests with coverage
cd apps/server && npx vitest run __tests__/unit/auth.test.ts
cd apps/server && npx vitest run --config vitest.integration.config.ts __tests__/integration/api.test.ts
cd apps/server && npx vitest __tests__/unit/
cd apps/server && npx vitest --config vitest.integration.config.ts __tests__/integration/
cd apps/server && npx playwright test __tests__/e2e/tunnel.spec.ts
cd apps/server && npx playwright test --headed # With browser visible
```
```bash
npm run build # Build all packages
npm run lint # Lint all packages
```
```bash
npm run db:generate # Generate Prisma client
npm run db:push # Push schema changes
cd apps/server && npx prisma studio # Open Prisma Studio GUI
cd apps/server && npx prisma migrate dev --name migration_name
```
```bash
cd apps/cli && npm run build # Build CLI
npm link # Link CLI globally for local development
lt --port 3000 # Create HTTP tunnel
lt --port 22 --tcp # Create TCP tunnel (for SSH, databases, etc.)
```
1. CLI connects via WebSocket and sends `REGISTER` message
2. Server creates tunnel entry, returns `REGISTERED` with public URL
3. HTTP requests to `subdomain.{TUNNEL_DOMAIN}` are captured by server
4. Server sends `REQUEST` message to CLI over WebSocket
5. CLI makes local HTTP request, returns `RESPONSE` message
6. Server responds to original HTTP request
1. CLI connects via WebSocket with `protocol: TCP` in `REGISTER` message
2. Server allocates a TCP port from configured range, returns `REGISTERED` with `tcpPort`
3. External TCP connections to `{TUNNEL_DOMAIN}:{tcpPort}` trigger `TCP_CONNECT` message
4. Data is relayed via `TCP_DATA` messages (Base64 encoded)
5. Connection closure sends `TCP_CLOSE` message
**Client→Server:** `REGISTER`, `RESPONSE`, `PING`
**Server→Client:** `REGISTERED`, `REQUEST`, `ERROR`, `PONG`
**TCP-specific:** `TCP_CONNECT`, `TCP_DATA`, `TCP_CLOSE`, `TCP_ERROR`
All types defined in `packages/shared/src/types.ts`.
All API routes should use the standardized wrappers for consistent error handling:
```typescript
import { withApiHandler, withAuth, withAdminAuth, success, ApiException, getParam, parseBody } from '@/lib/api';
// Public route with error handling
export const GET = withApiHandler(async (request, { params, logger }) => {
return success(data);
});
// Protected route requiring authentication
export const POST = withAuth(async (request, { user, logger, params }) => {
return success(data);
});
// Admin-only route
export const DELETE = withAdminAuth(async (request, { user, logger, params }) => {
return success(data);
});
```
Use `ApiException` factory methods:
Required and optional configuration:
```env
DATABASE_URL="file:./dev.db" # SQLite path
TUNNEL_DOMAIN="localhost:3000" # Public domain for tunnel URLs
TUNNEL_PORT=7000 # WebSocket server port
TUNNEL_REQUEST_TIMEOUT=30000 # Request timeout (5s-5min, default 30s)
TCP_PORT_RANGE_START=10000 # Starting port for TCP tunnels
TCP_PORT_RANGE_END=20000 # Ending port for TCP tunnels
CORS_ALLOWED_ORIGINS=... # Comma-separated (defaults to localhost:3000,3001)
NEXTAUTH_SECRET=... # Generate with: openssl rand -base64 32
NEXTAUTH_URL=http://localhost:3000
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=...
SMTP_PASS=...
FIREBASE_SERVICE_ACCOUNT='{"type":"service_account",...}'
FIREBASE_SERVICE_ACCOUNT_PATH="./firebase-service-account.json"
NEXT_PUBLIC_FIREBASE_API_KEY=...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=...
NEXT_PUBLIC_FIREBASE_PROJECT_ID=...
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=...
NEXT_PUBLIC_FIREBASE_APP_ID=...
NEXT_PUBLIC_FIREBASE_VAPID_KEY=...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_STARTER_MONTHLY=price_...
STRIPE_PRICE_PRO_MONTHLY=price_...
PAYMOB_API_KEY=...
PAYMOB_INTEGRATION_ID_CARD=...
PAYMOB_HMAC_SECRET=...
PAYTABS_PROFILE_ID=...
PAYTABS_SERVER_KEY=...
PAYTABS_REGION=SAU # SAU, ARE, EGY, OMN, JOR, GLO
PADDLE_VENDOR_ID=...
PADDLE_API_KEY=...
PADDLE_WEBHOOK_SECRET=...
PADDLE_SANDBOX=true
```
**Configurations:**
Vitest globals are enabled (`describe`, `it`, `expect`, `vi` available without imports). Unit tests include `@testing-library/jest-dom` for DOM assertions. Setup file: `apps/server/vitest.setup.ts`.
Key Prisma models in `apps/server/prisma/schema.prisma`:
The server uses `@/` alias pointing to `apps/server/src/`.
Example: `import { prisma } from '@/lib/db/prisma'`
Strict mode enabled with TypeScript 5.x and Next.js plugin for enhanced type checking.
1. **Always generate Prisma client** after schema changes: `npm run db:generate`
2. **Use API wrappers** (`withApiHandler`, `withAuth`) for all routes
3. **Follow error handling patterns** with `ApiException` factory methods
4. **Write tests** for all new features (unit, integration, E2E as appropriate)
5. **Use path aliases** (`@/`) for imports in the server package
6. **Check environment variables** before adding new config dependencies
7. **Update i18n files** when adding user-facing strings
8. **Document WebSocket message types** in `packages/shared/src/types.ts`
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/localhost-tunnel-development/raw