Database & Prisma Guide

PostgreSQL schema design, Prisma ORM setup, API route patterns, and the data seeding strategy.

Overview

The application uses PostgreSQL as its primary database, accessed via Prisma ORM. The schema is defined in prisma/schema.prisma and includes four models: User, Post, Course, and Contact.

Prisma provides type-safe database access, automatic migration generation, a built-in data browser (Prisma Studio), and a seed script for initial data population.

Schema Models

The schema uses the postgresql provider with the connection string from the DATABASE_URL environment variable.

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

User Model

FieldTypeNotes
idString (cuid)Primary key
emailStringUnique, used for login
passwordStringBcrypt hashed
nameString?Display name
roleRole enumUSER or ADMIN (default USER)
postsPost[]One-to-many relation
createdAtDateTimeAuto-set
updatedAtDateTimeAuto-updated

The Role enum has two values: USER and ADMIN. The admin middleware checks role === "ADMIN" for protected routes.

Post Model

FieldTypeNotes
idString (cuid)Primary key
titleStringPost title
slugStringUnique, URL-friendly identifier
excerptStringShort summary for listings
contentStringFull content (plain text or HTML)
categoryStringCategory for filtering (e.g. "Javascript", "CSS")
authorStringAuthor name (display)
coverImageString?Optional cover image URL
publishedBooleanPublished status (default false)
publishedAtDateTime?Publication date
userIdString?FK to User (optional)
Content format: The content field supports both plain text (paragraphs separated by \n\n) and HTML. When HTML is detected (<[a-z] regex), the RichContent component renders it with syntax highlighting and sanitization.

Course Model

FieldTypeNotes
idString (cuid)Primary key
titleStringCourse title
descriptionStringFull description
instructorStringInstructor name
durationStringe.g. "4 Months"
priceFloatPrice (0 for free courses)
originalPriceFloat?Strikethrough price (optional)
startDateString?Start date text
scheduleString?Schedule text
imageString?Course image URL
registrationLinkString?External registration URL
publishedBooleanPublished status (default true)

Contact Model

FieldTypeNotes
idString (cuid)Primary key
nameStringSubmitter's name
emailStringSubmitter's email
subjectStringMessage subject
messageStringFull message text
isReadBooleanRead status (default false)
createdAtDateTimeSubmission timestamp

Prisma Client Singleton

To prevent multiple Prisma Client instances in Next.js development (hot reload), the client is exported as a singleton from src/lib/prisma.ts:

import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const prisma = globalForPrisma.prisma || new PrismaClient();

if (process.env.NODE_ENV !== "production")
  globalForPrisma.prisma = prisma;

All database access imports prisma from this file.

API Routes

CRUD operations are exposed as Next.js API routes under src/app/api/:

EndpointMethodsDescription
/api/postsGET, POSTList all posts / create a post
/api/posts/[id]GET, PUT, DELETERead / update / delete a post
/api/coursesGET, POSTList all courses / create a course
/api/courses/[id]GET, PUT, DELETERead / update / delete a course
/api/contactGET, POSTList contacts / submit contact form
/api/auth/[...nextauth]GET, POSTNextAuth.js handler

POST/PUT routes validate input with Zod schemas before database writes. Validation errors return 400 with error details.

Server Component Queries

Public pages query the database directly in Server Components without an API round-trip:

// src/app/posts/page.tsx (Server Component)
import { prisma } from "@/lib/prisma";

async function getPosts() {
  try {
    return await prisma.post.findMany({
      where: { published: true },
      orderBy: { publishedAt: "desc" },
    });
  } catch {
    return []; // fallback: hardcoded data
  }
}

Each public page includes a try/catch with static fallback data so the site renders even without a database connection.

Seed Script

The seed script at prisma/seed.ts uses upsert for idempotent seeding. It creates:

npm run db:seed   # Runs: tsx prisma/seed.ts