Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/syhily/yufan.me/llms.txt

Use this file to discover all available pages before exploring further.

yufan.me requires two data stores: Postgres for all content, settings, sessions, and analytics, and Redis for session storage, rate limiting, and generated-image caching. Both must be reachable before the server starts. Schema management is handled by Drizzle Kit — migrations live under drizzle/ and are applied independently of application boot.

Postgres

yufan.me targets Postgres 16 or later. Postgres 17 is recommended. The pgvector extension is used for embedding-based search but is optional — the application falls back to SQL LIKE search when the extension is unavailable. Connection strings follow the standard libpq URI format:
DATABASE_URL=postgres://user:password@host:5432/database
If you are using SSL, append ?sslmode=require (or verify-full for certificate verification):
DATABASE_URL=postgres://user:password@host:5432/database?sslmode=require
The application connects via the pg driver through Drizzle ORM. All queries go through the pool configured in src/server/infra/db/.

Redis

Redis is used for three purposes:
  • Session storage — signed session tokens are persisted in Redis so they survive server restarts.
  • Rate limiting — per-IP and per-user counters enforce the limits configured in blog.rateLimit settings.
  • Caching — generated OG images and other computed artifacts are cached to avoid redundant work.
Connection strings follow the ioredis URI format:
REDIS_URL=redis://host:6379
For Redis with authentication:
REDIS_URL=redis://:password@host:6379
Redis Sentinel and Cluster topologies are not required — a single standalone Redis instance is sufficient for most deployments.

Running migrations

Migrations are managed with Drizzle Kit. The drizzle/ directory contains one subdirectory per migration, each with a SQL file and snapshot metadata. The directory is copied into the Docker runtime image so migrations can be applied from within a container without access to the source tree.
1

Set DATABASE_URL

Ensure DATABASE_URL is exported in your shell or present in your .env file before running any migration commands:
export DATABASE_URL=postgres://user:password@host:5432/database
Drizzle Kit reads this variable automatically. No separate drizzle.config.ts override is needed for the standard deployment path.
2

Generate migrations after schema changes (development only)

When you modify src/server/infra/db/schema.ts, generate a new migration file with:
npm run db:generate
This runs drizzle-kit generate and writes a new timestamped directory under drizzle/. Commit the generated files alongside your schema changes. You do not need to run this step when deploying an existing release.
3

Apply migrations

Apply all pending migrations against your database using drizzle-kit migrate:
npx drizzle-kit migrate
When deploying with Docker, run migrations from a temporary container before starting the application container:
docker run --rm --env-file .env yufan.me \
  npx drizzle-kit migrate
Drizzle Kit tracks applied migrations in a __drizzle_migrations table and skips files that have already been applied. It is safe to run on every deployment.

Migration files

The drizzle/ directory contains ten migrations applied in timestamp order:
MigrationDescription
20260514000000_enable_pgvectorEnables the pgvector extension for embedding-based search. Must be the first migration to run.
20260514000001_init_schemaInitial schema: post, page, category, tag, user, setting, image, music, friend, and junction tables.
20260514000002_access_logAnalytics visit ingestion table (access_log) used by the first-party analytics dashboard.
20260514000003_access_log_timescaleConverts access_log to a TimescaleDB hypertable for time-series performance. Falls back gracefully when TimescaleDB is not installed.
20260515153336_audit_logAudit log table that records admin events (content changes, settings updates, user actions).
20260517143442_sidebar_widgetsSidebar widget configuration stored as JSONB under blog.sidebar settings.
20260517150000_x_rebrand_and_footer_navUpdates social link keys to reflect the X (formerly Twitter) rebrand and adds footer navigation columns.
20260517160000_merge_footer_nav_into_navigationConsolidates footer navigation configuration into the blog.navigation settings section.
20260517170000_merge_footer_into_generalMerges footer display settings into the blog.general settings section.
20260521000000_audit_log_indexesAdds performance indexes to audit_log to speed up admin dashboard queries.
The audit_log table is excluded from standard pg_dump backups because it uses S3 archival for long-term retention. If you are running manual backups, add --exclude-table=audit_log to your pg_dump command to avoid backing up data that is already archived to object storage.
The pgvector extension is required for embedding-based search. If your Postgres provider does not support pgvector (for example, a standard cloud Postgres without extensions), the first migration will fail. In that case, comment out or skip 20260514000000_enable_pgvector and the application will automatically fall back to SQL LIKE search across post titles and bodies. All other features remain unaffected.

Build docs developers (and LLMs) love