Admin数据库

Prisma Development Guide

PostgreSQL + Prisma Database Design and Operations

Schema Design · Common Operations · Best Practices


🎯 Overview

NextJS Base uses PostgreSQL as the database and Prisma as the ORM. This guide explains how to design data models and perform database operations.

Tech Stack

ComponentVersionDescription
PostgreSQL16+Relational database
Prisma5.xType-safe ORM
Prisma ClientAuto-generatedDatabase client

📐 Schema Design

File Location

prisma/
└── schema.prisma    # Data model definition

Basic Structure

// Generator Configuration
generator client {
  provider = "prisma-client-js"
  output   = "../lib/generated/prisma"
}

// Data Source Configuration
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// Data Models
model User {
  id    String @id @default(cuid())
  email String @unique
  name  String?
  // ...
}

Common Field Types

Prisma TypePostgreSQL TypeDescription
StringTEXTText
IntINTEGERInteger
FloatDOUBLE PRECISIONFloat
DecimalDECIMALPrecise decimal
BooleanBOOLEANBoolean
DateTimeTIMESTAMPDateTime
JsonJSONBJSON data
String[]TEXT[]String array

Common Attributes

model Example {
  // Primary Key
  id        String    @id @default(cuid())
  
  // Unique Constraint
  email     String    @unique
  
  // Default Value
  enable    Boolean   @default(true)
  sort      Int       @default(0)
  
  // Optional Field
  remark    String?
  
  // Array Field
  tags      String[]  @default([])
  
  // Long Text
  content   String    @db.Text
  
  // Precise Decimal
  price     Decimal   @db.Decimal(10, 2)
  
  // Timestamps
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  
  // Soft Delete
  deletedAt DateTime?
  
  // Indexes
  @@index([createdAt])
  @@index([enable, sort])
}

📝 Model Design Examples

Example: Post Model

model Post {
  id          String    @id @default(cuid())
  
  // Basic Info
  title       String                        // Title
  slug        String    @unique             // URL alias
  content     String    @db.Text            // Content (Markdown)
  excerpt     String?                       // Excerpt
  cover       String?                       // Cover image
  
  // Category and Tags
  categoryId  String?                       // Category ID
  tags        String[]  @default([])        // Tag array
  
  // Status
  status      String    @default("draft")   // draft/published/archived
  enable      Boolean   @default(true)
  
  // Statistics
  viewCount   Int       @default(0)
  likeCount   Int       @default(0)
  
  // Author
  authorId    String
  
  // SEO
  metaTitle       String?
  metaDescription String?
  metaKeywords    String?
  
  // Time
  publishedAt DateTime?
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt
  deletedAt   DateTime?
  
  // Indexes
  @@index([status, enable])
  @@index([authorId])
  @@index([categoryId])
  @@index([publishedAt])
}

Example: Category Model (Tree Structure)

model Category {
  id        String    @id @default(cuid())
  
  name      String                        // Category name
  slug      String    @unique             // URL alias
  parentId  String?                       // Parent ID (supports tree)
  
  icon      String?                       // Icon
  cover     String?                       // Cover
  
  sort      Int       @default(0)
  enable    Boolean   @default(true)
  remark    String?
  
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  
  @@index([parentId])
  @@index([enable, sort])
}

🔧 Common Operations

Database Migration

# Create migration (development)
npx prisma migrate dev --name add_post_model

# Apply migration (production)
npx prisma migrate deploy

# Reset database (will delete all data)
npx prisma migrate reset

# Generate Prisma Client
npx prisma generate

Basic Queries

import { prisma } from '@/lib/database/prisma'

// Query Single
const user = await prisma.user.findUnique({
  where: { id: 'xxx' }
})

// Query Single (Multiple Conditions)
const user = await prisma.user.findFirst({
  where: { email: '[email protected]', enable: true }
})

// Query List
const users = await prisma.user.findMany({
  where: { enable: true },
  orderBy: { createdAt: 'desc' },
  take: 10,
  skip: 0,
})

// Query Count
const count = await prisma.user.count({
  where: { enable: true }
})

Create and Update

// Create Single
const user = await prisma.user.create({
  data: {
    email: '[email protected]',
    name: 'Test User',
  }
})

// Create Multiple
const users = await prisma.user.createMany({
  data: [
    { email: '[email protected]', name: 'User 1' },
    { email: '[email protected]', name: 'User 2' },
  ]
})

// Update Single
const user = await prisma.user.update({
  where: { id: 'xxx' },
  data: { name: 'New Name' }
})

// Update Multiple
const result = await prisma.user.updateMany({
  where: { enable: false },
  data: { enable: true }
})

Delete Operations

// Delete Single
await prisma.user.delete({
  where: { id: 'xxx' }
})

// Delete Multiple
await prisma.user.deleteMany({
  where: { enable: false }
})

// Soft Delete (Recommended)
await prisma.user.update({
  where: { id: 'xxx' },
  data: { deletedAt: new Date() }
})

Advanced Queries

// Fuzzy Search
const users = await prisma.user.findMany({
  where: {
    name: { contains: 'John' }
  }
})

// Range Query
const posts = await prisma.post.findMany({
  where: {
    createdAt: {
      gte: new Date('2024-01-01'),
      lte: new Date('2024-12-31'),
    }
  }
})

// Array Contains
const posts = await prisma.post.findMany({
  where: {
    tags: { has: 'javascript' }
  }
})

// OR Condition
const users = await prisma.user.findMany({
  where: {
    OR: [
      { name: { contains: 'John' } },
      { email: { contains: 'john' } },
    ]
  }
})

// Relation Query
const posts = await prisma.post.findMany({
  include: {
    author: true,
    category: true,
  }
})

🏗️ Integration with BaseDAO

BaseDAO Configuration

const postConfig = {
  modelName: 'post',
  primaryKey: 'id',
  softDelete: true,
  
  fields: {
    creatable: ['title', 'slug', 'content', 'categoryId', 'tags', 'status'],
    updatable: ['title', 'slug', 'content', 'categoryId', 'tags', 'status'],
    searchable: ['title', 'content', 'tags'],
  },
  
  query: {
    defaultSort: { createdAt: 'desc' },
    defaultPageSize: 20,
  },
}

Search Condition Conversion

BaseDAO automatically converts whereJson to Prisma query conditions:

// whereJson from frontend
{
  title: { contains: 'Tutorial' },
  status: 'published',
  createdAt: {
    gte: '2024-01-01',
    lte: '2024-12-31'
  }
}

// Prisma where converted by BaseDAO
{
  AND: [
    { title: { contains: 'Tutorial' } },
    { status: 'published' },
    { createdAt: { gte: new Date('2024-01-01'), lte: new Date('2024-12-31') } },
    { deletedAt: null }  // Soft delete filter
  ]
}

✅ Best Practices

1. Naming Conventions

TypeConventionExample
Model NamePascalCase, singularUser, Post, Category
Field NamecamelCasecreatedAt, userId, isPublished
Table NameAuto-converted to snake_caseusers, posts, categories

2. Required Fields

model Example {
  id        String    @id @default(cuid())  // Primary key
  createdAt DateTime  @default(now())       // Created time
  updatedAt DateTime  @updatedAt            // Updated time
  deletedAt DateTime?                       // Soft delete (recommended)
}

3. Use Indexes

model Post {
  // ... field definitions
  
  // Single Field Index
  @@index([createdAt])
  
  // Composite Index (common query conditions)
  @@index([status, enable, createdAt])
  
  // Foreign Key Index
  @@index([authorId])
  @@index([categoryId])
}

4. Soft Delete

model Post {
  // ...
  deletedAt DateTime?  // Soft delete field
}
// BaseDAO automatically handles soft delete
const postConfig = {
  softDelete: true,  // Enable soft delete
  // ...
}

// Automatically filter deleted records when querying
// Automatically set deletedAt instead of physical delete when deleting

5. Array Fields

model Post {
  tags String[] @default([])  // Tag array
}
// Query posts containing specific tag
await prisma.post.findMany({
  where: {
    tags: { has: 'javascript' }
  }
})

// Query posts containing multiple tags
await prisma.post.findMany({
  where: {
    tags: { hasEvery: ['javascript', 'react'] }
  }
})