Scaffold a Prisma 6.x project with schema.prisma (datasource, generator, models, relations, enums), migrations, seed script, Prisma Client usage, type-safe queries, and middleware.
79
73%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./database/prisma-schema-starter/SKILL.mdScaffold a Prisma 6.x project with schema.prisma (datasource, generator, models, relations, enums), migrations, seed script, Prisma Client usage, type-safe queries, and middleware.
npm install @prisma/client
npm install -D prisma typescript @types/node tsx
npx prisma init --datasource-provider postgresql
# Creates:
# prisma/schema.prisma
# .env (with DATABASE_URL placeholder)# Create the database and apply schema
npx prisma migrate dev --name init
# Seed initial data (if seed script exists)
npx prisma db seed
# Reset database (development only — drops and recreates)
npx prisma migrate resetprisma/
schema.prisma # Data model, datasource, generators
migrations/ # Auto-generated migration SQL files
seed.ts # Database seed script
src/
lib/
prisma.ts # Singleton Prisma Client instance
modules/
users/
users.repository.ts # Data access using Prisma Clientschema.prisma file at prisma/schema.prisma (default path).@id with @default(uuid()) or @default(cuid()) for primary keys. Avoid auto-increment integers for distributed systems.npx prisma generate after every schema change to regenerate the client.npx prisma migrate dev during development; npx prisma migrate deploy in CI/production.seed script to package.json for prisma db seed.prisma/schema.prisma)generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum Role {
USER
ADMIN
}
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
model User {
id String @id @default(uuid())
email String @unique
name String
password String
role Role @default(USER)
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
model Profile {
id String @id @default(uuid())
bio String?
avatar String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String @unique
@@map("profiles")
}
model Post {
id String @id @default(uuid())
title String
content String?
status PostStatus @default(DRAFT)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId String
tags Tag[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
@@index([status])
@@map("posts")
}
model Tag {
id String @id @default(uuid())
name String @unique
posts Post[]
@@map("tags")
}src/lib/prisma.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", "warn", "error"]
: ["error"],
});
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}import { PrismaClient } from "@prisma/client";
// Soft-delete extension
const prisma = new PrismaClient().$extends({
query: {
$allModels: {
async findMany({ args, query }) {
args.where = { ...args.where, deletedAt: null };
return query(args);
},
async delete({ args, query }) {
return (query as any)({
...args,
data: { deletedAt: new Date() },
});
},
},
},
});
export { prisma };src/modules/users/users.repository.ts)import { prisma } from "@/lib/prisma";
import type { Prisma } from "@prisma/client";
export async function createUser(data: Prisma.UserCreateInput) {
return prisma.user.create({
data,
select: {
id: true,
email: true,
name: true,
role: true,
createdAt: true,
},
});
}
export async function findUserByEmail(email: string) {
return prisma.user.findUnique({
where: { email },
include: { profile: true },
});
}
export async function findUserWithPosts(userId: string) {
return prisma.user.findUnique({
where: { id: userId },
include: {
posts: {
where: { status: "PUBLISHED" },
orderBy: { createdAt: "desc" },
take: 10,
},
},
});
}
export async function listUsers(params: {
page: number;
limit: number;
role?: "USER" | "ADMIN";
}) {
const { page, limit, role } = params;
const where: Prisma.UserWhereInput = role ? { role } : {};
const [users, total] = await prisma.$transaction([
prisma.user.findMany({
where,
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: "desc" },
select: {
id: true,
email: true,
name: true,
role: true,
createdAt: true,
_count: { select: { posts: true } },
},
}),
prisma.user.count({ where }),
]);
return { users, total, pages: Math.ceil(total / limit) };
}// Required models for @auth/prisma-adapter (NextAuth v5)
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}Add these relations to the User model: accounts Account[] and sessions Session[].
prisma/seed.ts)import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
// Upsert to make seed idempotent
const admin = await prisma.user.upsert({
where: { email: "admin@example.com" },
update: {},
create: {
email: "admin@example.com",
name: "Admin User",
password: "$2b$10$hashedpassword", // Use bcrypt in real seeds
role: "ADMIN",
profile: {
create: {
bio: "System administrator",
},
},
},
});
const tags = await Promise.all(
["TypeScript", "Prisma", "Node.js"].map((name) =>
prisma.tag.upsert({
where: { name },
update: {},
create: { name },
})
)
);
await prisma.post.upsert({
where: { id: "seed-post-1" },
update: {},
create: {
id: "seed-post-1",
title: "Getting Started with Prisma",
content: "Prisma is a next-generation ORM for Node.js and TypeScript.",
status: "PUBLISHED",
authorId: admin.id,
tags: { connect: tags.map((t) => ({ id: t.id })) },
},
});
console.log("Seed completed");
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});{
"prisma": {
"seed": "tsx prisma/seed.ts"
}
}# Generate Prisma Client after schema changes
npx prisma generate
# Create and apply a migration (development)
npx prisma migrate dev --name <migration-name>
# Apply pending migrations (CI/production)
npx prisma migrate deploy
# Reset database (drop, recreate, apply migrations, seed)
npx prisma migrate reset
# Seed database
npx prisma db seed
# Open Prisma Studio (visual database editor)
npx prisma studio
# Format schema file
npx prisma format
# Push schema without migrations (prototyping only)
npx prisma db push
# Pull schema from existing database
npx prisma db pull
# Check migration status
npx prisma migrate status@prisma/client directly in services or wrap in a NestJS module. Pair with nestjs-project-starter skill.@auth/prisma-adapter for session/account storage. Pair with nextauth-skill.jest-mock-extended or use a test database with prisma migrate reset before each test suite.db service in docker-compose. Run npx prisma migrate deploy in the app entrypoint or init container.181fcbc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.