Configuration

Database Setup

The Scanlation Template uses MongoDB as its database. This guide covers local setup with Docker and production options.

Local Development (Docker)

The easiest way to run MongoDB locally is using the included Docker Compose setup:

pnpm docker:up# or: make up

This starts MongoDB on port 27017. The default connection string is:

mongodb://localhost:27017/scanlation

Production Options

MongoDB AtlasRecommended

Free tier available. Managed, scalable, and reliable.

Get Started

Railway

Easy deployment with MongoDB plugin. Good for hobby projects.

Get Started

DigitalOcean

Managed MongoDB clusters with good pricing.

Get Started

Self-Hosted

Run MongoDB on your own VPS. More control, more responsibility.

View Docs

Setting Up MongoDB Atlas

  1. 1Create an account at mongodb.com/atlas
  2. 2Create a new cluster (M0 free tier works for small projects)
  3. 3Set up a database user with read/write access
  4. 4Add your IP address (or 0.0.0.0/0 for all IPs) to the IP Access List
  5. 5Get your connection string from "Connect" > "Connect your application"
  6. 6Replace <password> with your database user password
# Example Atlas connection string:
mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/scanlation?retryWrites=true&w=majority

Data Management

Backup Reminder: Always set up regular backups for your production database. MongoDB Atlas includes automated backups.

Useful Commands

# Export data (backup)
mongodump --uri="mongodb://localhost:27017/scanlation" --out=./backup
# Import data (restore)
mongorestore --uri="mongodb://localhost:27017/scanlation" ./backup/scanlation

Database Collections

The template uses the following collections (auto-created):

series

Manga/manhwa series metadata with i18n support

chapters

Individual chapter data and monetization settings

users

User accounts, premium status, and tokens

bookmarks

User reading progress and notifications

ratings

User ratings for series (1-5 stars)

comments

User comments on series and chapters

Collection Schemas

Below are the actual schema definitions used by the template. These are auto-created when data is first inserted.

Series Collection

{
  // Basic Info (with i18n support)
  title: {
    en: String (required),
    es?: String,
    pt?: String,
    ja?: String,
    zh?: String,
    ko?: String
  },
  slug: String (unique, lowercase),
  description: {
    en: String (required),
    es?: String,
    pt?: String,
    ...
  },
  
  // Media
  coverImage: String (required),
  bannerImage?: String,
  
  // Metadata
  author: String (required),
  artist?: String,
  genres: [String],
  tags: [String],
  
  // Status & Type
  status: 'ongoing' | 'completed' | 'hiatus' | 'cancelled',
  type: 'manga' | 'manhwa' | 'manhua' | 'webtoon' | 'comic',
  contentRating: 'safe' | 'suggestive' | 'mature' | 'explicit',
  
  // Stats (auto-updated)
  viewCount: Number,
  bookmarkCount: Number,
  chapterCount: Number,
  averageRating: Number (0-5),
  ratingCount: Number,
  
  // Publishing
  isPublished: Boolean,
  isFeatured: Boolean,
  
  // Timestamps
  createdAt: Date,
  updatedAt: Date
}

Chapters Collection

{
  series: ObjectId (ref: 'Series'),
  
  // Chapter Info
  number: Number (required),
  volume?: Number,
  title?: String,
  
  // Images (paths to cloud storage)
  images: [String] (required, min: 1),
  pageCount: Number (required),
  
  // Monetization
  isPremium: Boolean (default: false),
  tokenCost: Number (default: 0),
  freeAfterDays: Number (default: 0),
  publishedAt: Date,
  
  // Stats
  viewCount: Number,
  
  // Publishing
  isPublished: Boolean (default: true),
  
  // Virtual: isFreeNow (computed from freeAfterDays + publishedAt)
  
  // Timestamps
  createdAt: Date,
  updatedAt: Date
}

Users Collection

{
  // Auth (from OAuth provider)
  email: String (unique, required),
  name: String (required),
  image?: String,
  provider: 'google' | 'discord' | 'twitter',
  providerId: String,
  
  // Profile
  username?: String (unique, 3-20 chars),
  bio?: String (max: 500),
  
  // Flags
  isAdmin: Boolean,
  isModerator: Boolean,
  isBanned: Boolean,
  
  // Premium/Subscription
  isPremium: Boolean,
  premiumExpiresAt?: Date,
  stripeCustomerId?: String,
  stripeSubscriptionId?: String,
  
  // Token System
  tokenBalance: Number (default: 0),
  
  // 2FA
  twoFactorEnabled: Boolean,
  twoFactorSecret?: String,
  backupCodes?: [String],
  
  // Stats
  chaptersRead: Number,
  level: Number,
  experience: Number,
  
  // Timestamps
  createdAt: Date,
  updatedAt: Date,
  lastLoginAt?: Date
}

Bookmarks Collection

{
  user: ObjectId (ref: 'User'),
  series: ObjectId (ref: 'Series'),
  
  // Reading Progress
  lastReadChapter?: ObjectId (ref: 'Chapter'),
  lastReadChapterNumber?: Number,
  lastReadAt?: Date,
  
  // Notifications
  notifyOnUpdate: Boolean (default: true),
  
  // Timestamps
  createdAt: Date,
  updatedAt: Date
}
// Unique index: { user, series }

Comments Collection

{
  user: ObjectId (ref: 'User'),
  series?: ObjectId (ref: 'Series'),
  chapter?: ObjectId (ref: 'Chapter'),
  parentComment?: ObjectId (ref: 'Comment'),
  
  // Content
  content: String (required, 1-2000 chars),
  
  // Reactions
  likes: [ObjectId] (refs: 'User'),
  dislikes: [ObjectId] (refs: 'User'),
  likeCount: Number,
  dislikeCount: Number,
  
  // Status
  isEdited: Boolean,
  isDeleted: Boolean,
  isReported: Boolean,
  
  // Reply tracking
  replyCount: Number,
  
  // Timestamps
  createdAt: Date,
  updatedAt: Date
}

Ratings Collection

{
  user: ObjectId (ref: 'User'),
  series: ObjectId (ref: 'Series'),
  
  // Rating (1-5 stars)
  rating: Number (required, min: 1, max: 5),
  
  // Timestamps
  createdAt: Date,
  updatedAt: Date
}
// Unique index: { user, series }