End-to-end type-safe API backend for TuvixRSS, built with tRPC. This package provides a portable API that can run on both Node.js (Docker) and Cloudflare Workers.
This is a portable API with 95% shared code between deployment targets:
- Shared: Routers, types, validation, business logic, cron handlers
- Swappable: Database driver (better-sqlite3 / D1), HTTP adapter (Express / Workers)
- ✅ End-to-end type safety with tRPC
- ✅ Runtime validation with Zod
- ✅ JWT authentication
- ✅ Portable architecture (Docker + Cloudflare Workers)
- ✅ SQLite with Drizzle ORM
- ✅ Background job scheduling (node-cron / Workers scheduled events)
- ✅ RSS feed parsing and management
- ✅ Content filtering and subscriptions
- ✅ Public RSS feed generation
- ✅ User plans and rate limiting
- ✅ Admin operations
- Admin Guide - Admin operations and user management
- Main Project Documentation - Complete documentation index
packages/api/
├── src/
│ ├── adapters/ # HTTP adapters
│ │ ├── express.ts # Node.js/Docker adapter
│ │ └── cloudflare.ts # Cloudflare Workers adapter
│ ├── auth/ # Authentication utilities
│ │ ├── better-auth.ts # Better Auth configuration
│ │ ├── password.ts # Password hashing (admin init only)
│ │ └── security.ts # Security audit logging
│ ├── cron/ # Scheduled jobs
│ │ ├── handlers.ts # Portable cron logic
│ │ └── scheduler.ts # Node.js cron setup
│ ├── db/ # Database
│ │ ├── schema.ts # Drizzle schema
│ │ └── client.ts # Portable DB client factory
│ ├── routers/ # tRPC routers
│ │ ├── auth.ts # ✅ Authentication (complete)
│ │ ├── articles.ts # ✅ Articles management
│ │ ├── subscriptions.ts # ✅ Subscriptions & filters
│ │ ├── categories.ts # ✅ Categories
│ │ ├── feeds.ts # ✅ Public feeds
│ │ ├── plans.ts # ✅ Plans management
│ │ ├── admin.ts # ✅ Admin operations
│ │ └── userSettings.ts # ✅ User settings
│ ├── services/ # Business logic services
│ │ ├── rss-fetcher.ts # ✅ RSS parsing
│ │ ├── favicon-fetcher.ts # ✅ Favicon discovery
│ │ ├── xml-generator.ts # ✅ RSS XML generation
│ │ ├── email.ts # ✅ Email service
│ │ ├── rate-limiter.ts # ✅ Rate limiting
│ │ └── global-settings.ts # ✅ Global settings
│ ├── trpc/ # tRPC core
│ │ ├── init.ts # tRPC initialization
│ │ ├── context.ts # Request context
│ │ └── router.ts # Root router
│ └── types.ts # Shared types & Zod schemas
├── data/ # Local development data (gitignored)
├── drizzle.config.ts # Drizzle Kit config
├── wrangler.toml # Cloudflare Workers config
├── tsconfig.json # TypeScript config
└── package.json
- Node.js 18+ (for Docker deployment)
- pnpm (package manager)
- SQLite (for local development)
cd packages/api
pnpm installCreate a .env file in the package root:
# Required
BETTER_AUTH_SECRET=your-secure-secret-key-here
# Optional (defaults shown)
DATABASE_PATH=./data/tuvix.db
PORT=3001
RUNTIME=nodejsGenerate and run migrations:
# Generate migration from schema
pnpm db:generate
# Apply migrations
pnpm db:migrate# Start development server with hot reload
pnpm dev
# Server runs on http://localhost:3001
# tRPC endpoint: http://localhost:3001/trpc
# Health check: http://localhost:3001/healthThe Express adapter automatically:
- Initializes the SQLite database
- Starts the tRPC server
- Starts cron jobs (RSS fetch every 15 minutes)
# Start local Workers environment
pnpm dev:workers
# Uses wrangler dev with:
# - Local D1 database
# - Cron trigger testing# Build
pnpm build
# Run with environment variables
NODE_ENV=production BETTER_AUTH_SECRET=your-secret node dist/adapters/express.jsPrerequisites:
- Cloudflare Workers Paid plan ($5/month) - Required for password hashing CPU limits
- D1 database created:
wrangler d1 create tuvix
Required Secrets:
# Authentication (minimum 32 characters)
wrangler secret put BETTER_AUTH_SECRET
# First user auto-promotion to admin
wrangler secret put ALLOW_FIRST_USER_ADMIN
# Enter: true
# Email service (Resend)
wrangler secret put RESEND_API_KEY
wrangler secret put EMAIL_FROM
# Example: noreply@yourdomain.com
# Application URLs
wrangler secret put BASE_URL
# Example: https://yourdomain.com
wrangler secret put API_URL
# Example: https://api.yourdomain.com
wrangler secret put CORS_ORIGIN
# Example: https://yourdomain.com
wrangler secret put COOKIE_DOMAIN
# Example: .yourdomain.com
# Sentry (optional)
wrangler secret put SENTRY_DSN
wrangler secret put SENTRY_ENVIRONMENT
# Example: productionDatabase Setup:
# 1. Create database
wrangler d1 create tuvix
# 2. Configure for local development:
# Option A: Create wrangler.toml.local (recommended)
cp wrangler.toml.local.example wrangler.toml.local
# Edit wrangler.toml.local and add your database_id
# Option B: Set environment variable
export D1_DATABASE_ID="<database-id-from-step-1>"
# For CI/CD: Set GitHub secret
gh secret set D1_DATABASE_ID --body "<database-id-from-step-1>"
# 3. Run migrations
pnpm db:migrate:d1Deploy:
# Deploy to Workers
pnpm deploy
# The deploy script automatically:
# - Creates wrangler.toml from wrangler.example.toml
# - Substitutes database_id from wrangler.toml.local or D1_DATABASE_ID env var
# - Deploys with CPU limits configured (30 seconds)
# - Applies cron trigger schedule (every 5 minutes)First Admin User:
After deployment, the first user to sign up automatically becomes admin (when ALLOW_FIRST_USER_ADMIN=true):
- Navigate to
https://yourdomain.com/sign-up - Register with your email
- You'll be assigned user ID 1 and admin role automatically
- Email verification is disabled by default (can be enabled in settings)
All procedures are available under the /trpc endpoint using tRPC's batching and protocol.
auth.register- Register new userauth.login- Login (Better Auth handles session via cookies)auth.me- Get current user (protected)
articles.list- List articles with filtersarticles.getById- Get single articlearticles.markRead- Mark article as readarticles.markUnread- Mark article as unreadarticles.save- Save articlearticles.unsave- Unsave articlearticles.bulkMarkRead- Bulk mark as readarticles.markAllRead- Mark all as readarticles.refresh- Trigger RSS refresh
subscriptions.list- List user subscriptionssubscriptions.getById- Get subscription detailssubscriptions.create- Create subscriptionsubscriptions.update- Update subscriptionsubscriptions.delete- Delete subscriptionsubscriptions.discover- Discover feeds from URLsubscriptions.preview- Preview feed before subscribingsubscriptions.parseOpml- Parse OPML contentsubscriptions.import- Import selected feeds from OPMLsubscriptions.importStatus- Check import progresssubscriptions.export- Export subscriptions as OPMLsubscriptions.listFilters- List subscription filterssubscriptions.createFilter- Create content filtersubscriptions.updateFilter- Update content filtersubscriptions.deleteFilter- Delete content filtersubscriptions.testFilter- Test filter against contentsubscriptions.reapplyFilters- Reapply filters to articles
categories.list- List user categoriescategories.getById- Get category detailscategories.create- Create categorycategories.update- Update categorycategories.delete- Delete categorycategories.suggestions- Get category suggestions from feed
feeds.list- List user's public feedsfeeds.getById- Get feed detailsfeeds.create- Create public feedfeeds.update- Update feedfeeds.delete- Delete feedfeeds.getPublicXml- Get RSS 2.0 XML (public endpoint)
userSettings.get- Get user settingsuserSettings.update- Update settings
Protected procedures require authentication via Better Auth session cookies. Sessions are automatically managed by Better Auth - no manual token handling needed.
Login by calling auth.login or auth.register. Better Auth handles session creation automatically.
- Define types in
types.ts:
export const MyInputSchema = z.object({
field: z.string(),
});
export const MyOutputSchema = z.object({
result: z.string(),
});- Add procedure to router:
// src/routers/my-router.ts
export const myRouter = router({
myProcedure: protectedProcedure
.input(MyInputSchema)
.output(MyOutputSchema)
.query(async ({ ctx, input }) => {
// Implementation
return { result: "success" };
}),
});- Add router to root:
// src/trpc/router.ts
export const appRouter = router({
// ... existing routers
myRouter: myRouter,
});When adding new features:
- Define types and schemas in
src/types/ - Add router procedures in
src/routers/ - Implement business logic in
src/services/if needed - Add tests in
__tests__/directories - Update this README with new endpoints
# Run type checking
pnpm type-check
# Run linting
pnpm lint
# Test with actual client
# Use @trpc/client or create a simple test scriptWhen you modify src/db/schema.ts:
# Generate migration
pnpm db:generate
# Review the generated SQL in drizzle/migrations/
# Apply migration
pnpm db:migrate# In your frontend package
pnpm add @trpc/client @trpc/react-query @tanstack/react-queryimport { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "@tuvix/api";
export const trpc = createTRPCReact<AppRouter>();function MyComponent() {
const { data, isLoading } = trpc.articles.list.useQuery({
filter: 'unread',
limit: 20,
});
return (
<div>
{data?.articles.map(article => (
<div key={article.id}>{article.title}</div>
))}
</div>
);
}Pros:
- Full control
- No cold starts
- Larger resource limits
Cons:
- Server maintenance required
- Single region (add CDN for multi-region)
- Manual scaling
Best for: Self-hosting, privacy-focused users, higher traffic
Pros:
- Global edge deployment (300+ locations)
- Auto-scaling
- Zero maintenance
- Built-in DDoS protection
- Free tier available
Cons:
- 128MB memory limit
- 30s CPU time limit
- Requires D1 setup
Best for: Public hosting, global users, low-maintenance
SQLite with WAL mode is enabled. If you see "database is locked":
# Check for stale WAL files
rm data/tuvix.db-wal
# Or restart the serverThe adapters include CORS headers. If you need to customize:
// In express.ts or cloudflare.ts
headers.set("Access-Control-Allow-Origin", "https://yourdomain.com");Always set a strong BETTER_AUTH_SECRET in production:
# Generate a secure secret
openssl rand -base64 32- Admin Guide - Admin operations and user management
- Documentation Index - Complete documentation index
- Deployment Guide - Docker & Cloudflare Workers deployment
- tRPC API Architecture - Complete API reference
- Project Integration - Frontend-backend integration guide
Same as parent project (TuvixRSS)
✅ Production Ready - All core features are implemented and tested.
The API is fully functional with complete CRUD operations for articles, subscriptions, categories, feeds, and user settings. All endpoints are documented and type-safe.