Manage Apple Reminders via the `remindctl` CLI on macOS (list, add, edit, complete, delete)....
npx skills add aussiegingersnap/cursor-skills --skill "db-sqlite"
Install specific skill from multi-skill repository
# Description
SQLite database management with Prisma ORM, type-safe queries, and Railway deployment with Litestream backup. This skill should be used when creating database schemas, writing migrations, managing SQLite on Railway volumes, or troubleshooting database issues.
# SKILL.md
name: db-sqlite
description: SQLite database management with Prisma ORM, type-safe queries, and Railway deployment with Litestream backup. This skill should be used when creating database schemas, writing migrations, managing SQLite on Railway volumes, or troubleshooting database issues.
SQLite Database Skill
Comprehensive patterns for SQLite database management in Node.js/TypeScript projects using Prisma ORM, including schema-first development and Railway deployment with Litestream backup.
When to Use This Skill
- Setting up SQLite in a new project
- Defining database schemas with Prisma
- Running migrations with
prisma migrate - Deploying SQLite to Railway with persistent volumes
- Backing up production databases with Litestream
- Troubleshooting database issues
Core Concepts
SQLite vs Network Databases
SQLite is appropriate when:
- Single server/container deployment
- Read-heavy workloads (or moderate writes)
- Simplicity is valued over horizontal scaling
- Local-first or embedded scenarios
- Cost-sensitive deployments
Consider PostgreSQL when:
- Multiple servers need database access
- Remote database inspection is required
- High write concurrency is expected
- Team needs direct database access for debugging
Railway Deployment Constraints
SQLite on Railway requires understanding these constraints:
- Volume mounting - Database file must live on a Railway volume (not container filesystem)
- No remote access - Cannot connect database GUI tools directly to production
- Single container - Only one instance can write to the database
- Backup strategy - Use Litestream for continuous backup to Railway Bucket
Critical: Railway Volume Path vs Container Path
This is the #1 cause of data loss on Railway SQLite deployments.
Railway volumes mount at a specific path (e.g., /data). But your app runs in /app/ by default. If your code writes to ./prisma/app.db, it creates the file at /app/prisma/app.db — which is NOT on the volume and gets destroyed on every deploy.
Solution: Set DATABASE_URL to use the volume path in production.
# Wrong (data lost on each deploy):
file:/app/prisma/app.db ← Container filesystem, not persistent
# Correct (data persists):
file:/data/app.db ← Railway volume, persistent + backed up
Database Setup Pattern
Package Installation
npm install @prisma/client
npm install -D prisma
Directory Structure
prisma/
├── schema.prisma # Database schema (source of truth)
└── migrations/ # Generated SQL migrations
src/lib/db/
└── index.ts # Prisma client singleton
Environment Configuration
# .env.local (development)
DATABASE_URL="file:./prisma/dev.db"
# Railway (production) — REQUIRED
# Must point to volume mount path
DATABASE_URL="file:/data/app.db"
Prisma Schema Setup
Create prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
// Define your models here
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Prisma Client Singleton
Create src/lib/db/index.ts:
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
})
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
export default prisma
Package.json Scripts
{
"scripts": {
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:migrate:prod": "prisma migrate deploy",
"db:push": "prisma db push",
"db:studio": "prisma studio",
"db:reset": "prisma migrate reset"
}
}
Schema Patterns
Primary Keys
Use CUID or UUID for user-facing entities:
model User {
id String @id @default(cuid())
// or: id String @id @default(uuid())
}
Timestamps
Always include created/updated timestamps:
model Post {
id String @id @default(cuid())
title String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Relations
One-to-many:
model User {
id String @id @default(cuid())
posts Post[]
}
model Post {
id String @id @default(cuid())
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
}
Many-to-many (explicit junction table):
model Post {
id String @id @default(cuid())
tags PostTag[]
}
model Tag {
id String @id @default(cuid())
name String @unique
posts PostTag[]
}
model PostTag {
postId String
tagId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([postId, tagId])
}
Enums
enum Role {
ADMIN
USER
GUEST
}
model User {
id String @id @default(cuid())
role Role @default(USER)
}
Indexes
model Post {
id String @id @default(cuid())
authorId String
published Boolean @default(false)
createdAt DateTime @default(now())
@@index([authorId])
@@index([published, createdAt])
}
Unique Constraints
model BookCollaborator {
id String @id @default(cuid())
bookId String
userId String
@@unique([bookId, userId])
}
Migration Workflow
Development
# Make schema changes in prisma/schema.prisma
# Create and apply migration
npm run db:migrate
# Prompts for migration name, e.g., "add_posts_table"
# Quick iteration (no migration file, just sync)
npm run db:push
Production
# Apply pending migrations (run in CI/CD or startup)
npm run db:migrate:prod
Migration Best Practices
- Never edit applied migrations - They may have run in production
- Name migrations descriptively -
add_user_avatar,create_posts_table - Review generated SQL - Check
prisma/migrations/before deploying - Use db:push for prototyping - Switch to migrations when schema stabilizes
- Commit migrations - They're part of your codebase
Query Patterns
Basic CRUD
import { prisma } from '@/lib/db'
// Create
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'John Doe',
},
})
// Read one
const user = await prisma.user.findUnique({
where: { email: '[email protected]' },
})
// Read many with filters
const users = await prisma.user.findMany({
where: {
role: 'ADMIN',
createdAt: { gte: new Date('2024-01-01') },
},
orderBy: { createdAt: 'desc' },
take: 10,
})
// Update
const updated = await prisma.user.update({
where: { id: userId },
data: { name: 'Jane Doe' },
})
// Delete
await prisma.user.delete({
where: { id: userId },
})
With Relations
// Include related data
const userWithPosts = await prisma.user.findUnique({
where: { id: userId },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
},
},
})
// Select specific fields
const userEmail = await prisma.user.findUnique({
where: { id: userId },
select: {
email: true,
posts: {
select: { title: true },
},
},
})
// Nested create
const userWithPost = await prisma.user.create({
data: {
email: '[email protected]',
posts: {
create: {
title: 'My First Post',
},
},
},
include: { posts: true },
})
Transactions
// Sequential operations
const [user, post] = await prisma.$transaction([
prisma.user.create({ data: { email: '[email protected]' } }),
prisma.post.create({ data: { title: 'Hello', authorId: 'temp' } }),
])
// Interactive transaction
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: { email: '[email protected]' },
})
await tx.post.create({
data: {
title: 'My Post',
authorId: user.id,
},
})
})
Upsert
const user = await prisma.user.upsert({
where: { email: '[email protected]' },
update: { name: 'Updated Name' },
create: { email: '[email protected]', name: 'New User' },
})
Railway Operations
Environment Variables
Required Railway configuration:
DATABASE_URL=file:/data/app.db
Ensure volume is mounted at /data.
Shell Access
# Open interactive shell in Railway container
railway shell
# Inside container - use Prisma Studio (opens web UI)
npx prisma studio
# Or use sqlite3 directly
sqlite3 /data/app.db
.tables
.schema User
SELECT * FROM User LIMIT 5;
.quit
Check Migration Status
railway shell
npx prisma migrate status
Continuous Backup with Litestream
Litestream provides real-time SQLite replication to S3-compatible storage. Combined with Railway Buckets, this gives you continuous backups without external providers.
See references/litestream.md for complete setup guide.
Quick Overview
- Litestream monitors SQLite WAL changes
- Streams changes to Railway Bucket every ~10 seconds
- On container restart, restores from bucket if local DB is missing
- ~10 second recovery point objective (RPO)
Minimal Setup
# 1. Create Railway Bucket
railway add --service bucket
# 2. Add litestream.yml to project root
# 3. Update nixpacks.toml to install litestream
# 4. Update railway.toml start command
# 5. Add restore script for empty volumes
Troubleshooting
"Cannot find module '@prisma/client'"
Generate the client after schema changes:
npm run db:generate
"Migration failed"
Check migration status and pending migrations:
npx prisma migrate status
For stuck migrations, you may need to mark as applied or reset:
# Mark a migration as applied (use with caution)
npx prisma migrate resolve --applied <migration_name>
# Reset database (development only)
npx prisma migrate reset
"Database is locked"
SQLite allows only one writer at a time. Solutions:
1. Keep transactions short
2. Avoid long-running queries during writes
3. Use connection pooling sparingly (usually singleton is fine)
Production Database Issues
- Check Litestream backup status
- Restore from backup if needed
- Review
references/litestream.mdfor restore procedures
References
references/boilerplate.md- Complete Prisma setup codereferences/migrations.md- Migration patterns and examplesreferences/litestream.md- Continuous backup setup with Railway Buckets- Prisma Docs - Official documentation
- Railway SQLite Guide - Railway-specific patterns
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.