Overview
Featul is a modern SaaS feedback management platform built as a full-stack TypeScript application. The architecture follows modern best practices with clear separation of concerns, type safety throughout, and scalability at its core.Tech Stack
- Runtime: Bun (development) / Node.js 20+ (production)
- Framework: Next.js 16 with App Router and Turbopack
- Language: TypeScript
- Database: PostgreSQL via Neon serverless
- ORM: Drizzle ORM
- API Layer: jstack (type-safe RPC over Hono)
- Authentication: better-auth
- State Management:
- Server state: TanStack Query (React Query)
- Client state: Zustand
- Styling: Tailwind CSS 4 + Radix UI
- Deployment: Vercel
Multi-Tenancy Architecture
Subdomain Routing
Featul uses subdomain-based multi-tenancy to provide each workspace with its own unique URL:apps/app/src/middleware/host.ts:
Custom Domains
Workspaces on Starter or Professional plans can use custom domains. The system uses:- CNAME Record: Points to
origin.featul.com - TXT Record: For DNS verification (
_acme-challenge.{domain}) - Domain Table:
workspaceDomainstores verification status
packages/db/schema/workspace.ts:30 for the domain schema.
Reserved Subdomains
The following subdomains are reserved for platform use:www- Marketing siteapp- Main applicationfeatul- Brand domainfeedgot- Legacy/alternate brandstaging- Staging environment
Data Architecture
Database Schema Organization
The database schema is organized by domain inpackages/db/schema/:
- auth.ts - User authentication and sessions
- workspace.ts - Workspaces, members, domains, invites
- feedback.ts - Boards and feedback configuration
- post.ts - Posts, tags, updates, reports, merges
- comment.ts - Comments, reactions, mentions, reports
- vote.ts - Votes and vote aggregates
- changelog.ts - Changelog entries
- branding.ts - Workspace branding configuration
- plan.ts - Subscription and billing
- integration.ts - Third-party integrations
- notra.ts - Notra connection data
Workspace Data Model
Each workspace is the top-level tenant entity:API Architecture
Type-Safe RPC with jstack
The API layer uses jstack for end-to-end type safety. jstack provides:- Type-safe client-server communication
- Built on Hono for performance
- Automatic type inference
- SuperJSON serialization for complex types
packages/api/src/index.ts:
Procedure Types
Two main procedure types are defined inpackages/api/src/jstack.ts:56:
- publicProcedure: For unauthenticated endpoints (viewing public boards)
- privateProcedure: Requires authentication, provides
ctx.session
Client Usage
From the frontend, API calls are fully type-safe:Authentication
better-auth Integration
Authentication uses better-auth with:- Email/password + OTP verification
- OAuth providers (Google, GitHub)
- Passkey support
- Two-factor authentication
- Organization plugin for workspace membership
Cross-Subdomain Sessions
Sessions work across subdomains using:- Cookie domain set to
.featul.com AUTH_COOKIE_DOMAINenvironment variable- Trusted origins configuration
Session Access
Server Components:State Management
Server State (TanStack Query)
All server data is managed with React Query:Client State (Zustand)
Local UI state uses Zustand stores inapps/app/src/lib/:
- selection-store: Multi-select functionality for posts
- branding-store: Theme and branding state
- filter-store: Board filtering and sorting
File Structure
Key Files
apps/app/src/app/api/[[...route]]/route.ts- API catch-all route handlerpackages/api/src/index.ts- API router aggregationpackages/api/src/jstack.ts- Middleware and procedure definitionspackages/auth/src/auth.ts- Authentication configurationpackages/db/index.ts- Database client and schema exportsapps/app/src/middleware/host.ts- Subdomain routing logic
Middleware Chain
Next.js middleware executes in this order:- Host detection - Identify subdomain and custom domain
- Authentication - Check session validity
- Routing - Rewrite URLs for workspace context
Performance Considerations
Database Optimization
- Neon serverless: Auto-scaling PostgreSQL
- Connection pooling: Built into Drizzle with Neon
- Prepared statements: Drizzle uses prepared statements by default
- Indexes: Strategic indexes on workspace_id, user_id, timestamps
API Optimization
- SuperJSON: Efficient serialization with type preservation
- Lazy router imports: Routers are dynamically imported
- Edge runtime: API routes can run on edge (Vercel)
Caching Strategy
Security
Data Isolation
All queries filter byworkspaceId to ensure tenant isolation:
CORS and Origin Validation
- Trusted origins configured via
API_TRUSTED_ORIGINS - Origin enforcement in
packages/api/src/shared/request-origin.ts - CORS middleware applied to all API routes
Input Validation
All inputs validated with Zod schemas inpackages/api/src/validators/.
Deployment
Build Process
Turborepo orchestrates the build:Environment Variables
Seeturbo.json:12 for the complete list of required environment variables.
Production Architecture
- Frontend: Deployed to Vercel (Next.js App Router)
- API: Serverless functions on Vercel
- Database: Neon serverless PostgreSQL
- File Storage: Cloudflare R2
- Email: Resend