```typescript
// ============================================
// Prisma Client Instance - db.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;
// ============================================
// Schema (schema.prisma)
// ============================================
/*
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql" // or mysql, sqlite, sqlserver
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
password String // Hashed
role Role @default(USER)
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
model Profile {
id Int @id @default(autoincrement())
bio String?
userId Int @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
viewCount Int @default(0)
authorId Int
author User @relation(fields: [authorId], references: [id])
categories Category[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
}
model Category {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
}
*/
// ============================================
// Services / Repositories
// ============================================
export const userService = {
async findByEmail(email: string) {
return prisma.user.findUnique({
where: { email },
include: { profile: true },
});
},
async create(data: { email: string; name: string; password: string }) {
return prisma.user.create({
data: {
...data,
profile: {
create: { bio: "New user" },
},
},
});
},
async getFeed(userId: number) {
// Get posts from authors followed by user (example logic)
return prisma.post.findMany({
where: {
published: true,
authorId: { not: userId }, // Exclude own posts
},
include: {
author: {
select: { name: true, email: true },
},
_count: {
select: { categories: true },
},
},
orderBy: { createdAt: "desc" },
take: 20,
});
},
};
export const postService = {
async create(userId: number, title: string, content?: string) {
return prisma.post.create({
data: {
title,
content,
authorId: userId,
},
});
},
async publish(postId: number, userId: number) {
// Verify ownership and update
const post = await prisma.post.findUnique({ where: { id: postId } });
if (!post) throw new Error("Post not found");
if (post.authorId !== userId) throw new Error("Not authorized");
return prisma.post.update({
where: { id: postId },
data: { published: true },
});
},
async delete(postId: number, userId: number) {
return prisma.post.deleteMany({
where: {
id: postId,
authorId: userId, // Ensure ownership
},
});
},
};
// ============================================
// Transaction Example
// ============================================
export async function transferOwnership(postId: number, newAuthorId: number) {
return prisma.$transaction(async (tx) => {
const post = await tx.post.findUniqueOrThrow({ where: { id: postId } });
// Log the transfer
console.log(`Transferring post ${post.id} to user ${newAuthorId}`);
const updated = await tx.post.update({
where: { id: postId },
data: { authorId: newAuthorId },
});
return updated;
});
}
// ============================================
// Pagination Helper
// ============================================
export async function getPostsPaginated(cursor?: number, limit = 10) {
return prisma.post.findMany({
take: limit,
skip: cursor ? 1 : 0, // Skip the cursor itself
cursor: cursor ? { id: cursor } : undefined,
orderBy: { id: "desc" },
where: { published: true },
});
}
// ============================================
// Middleware Example (Soft Delete)
// ============================================
/*
prisma.$use(async (params, next) => {
// Check incoming query type
if (params.model == 'Post' && params.action == 'delete') {
// Change action to an update
params.action = 'update'
params.args['data'] = { deleted: true }
}
return next(params)
})
*/
// ============================================
// Clean Shutdown
// ============================================
export async function disconnect() {
await prisma.$disconnect();
}
```