Next.js with Prisma — Database Integration

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Next.js with Prisma — Database Integration

Prisma provides type-safe database access with an intuitive query interface, making data management in Next.js applications straightforward.

Installation

npm install @prisma/client
npm install -D prisma
npx prisma init

Database Connection

Set environment variable:

# .env.local
DATABASE_URL="postgresql://user:password@localhost:5432/dbname"

Schema Definition

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

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

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

model Post {
  id    Int     @id @default(autoincrement())
  title String
  content String
  published Boolean @default(false)
  author    User    @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

Migrations

# Create migration
npx prisma migrate dev --name init

# Apply migrations
npx prisma migrate deploy

# Reset database
npx prisma migrate reset

Prisma Client Setup

// lib/prisma.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = global as unknown as { prisma: PrismaClient }

export const prisma =
  globalForPrisma.prisma ||
  new PrismaClient({
    log: ['query']
  })

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

CRUD Operations

Create:

// Create a user
const user = await prisma.user.create({
  data: {
    email: 'user@example.com',
    name: 'John Doe'
  }
})

// Create with relations
const post = await prisma.post.create({
  data: {
    title: 'My Post',
    content: 'Content here',
    author: {
      connect: { id: userId }
    }
  }
})

Read:

// Find one
const user = await prisma.user.findUnique({
  where: { email: 'user@example.com' }
})

// Find many
const posts = await prisma.post.findMany({
  where: { published: true },
  include: { author: true },
  orderBy: { createdAt: 'desc' }
})

// Find first
const latestPost = await prisma.post.findFirst({
  orderBy: { createdAt: 'desc' }
})

Update:

const user = await prisma.user.update({
  where: { id: userId },
  data: { name: 'Jane Doe' }
})

Delete:

await prisma.post.delete({
  where: { id: postId }
})

API Routes with Prisma

// app/api/posts/route.ts
import { prisma } from '@/lib/prisma'
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const posts = await prisma.post.findMany({
    include: { author: true },
    orderBy: { createdAt: 'desc' }
  })

  return NextResponse.json(posts)
}

export async function POST(request: NextRequest) {
  const { title, content, authorId } = await request.json()

  const post = await prisma.post.create({
    data: { title, content, authorId },
    include: { author: true }
  })

  return NextResponse.json(post, { status: 201 })
}

Dynamic Routes

// app/blog/[id]/page.tsx
import { prisma } from '@/lib/prisma'
import { notFound } from 'next/navigation'

export async function generateStaticParams() {
  const posts = await prisma.post.findMany({
    select: { id: true }
  })

  return posts.map(post => ({
    id: post.id.toString()
  }))
}

export default async function PostPage({ params }) {
  const post = await prisma.post.findUnique({
    where: { id: parseInt(params.id) },
    include: { author: true }
  })

  if (!post) notFound()

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <p>By {post.author.name}</p>
    </article>
  )
}

Advanced Queries

// Complex queries
const user = await prisma.user.findUnique({
  where: { id: userId },
  include: {
    posts: {
      where: { published: true },
      orderBy: { createdAt: 'desc' },
      take: 10
    }
  }
})

// Aggregation
const count = await prisma.post.count({
  where: { published: true }
})

// Grouping
const grouped = await prisma.post.groupBy({
  by: ['authorId'],
  _count: true
})

// Raw queries
const results = await prisma.$queryRaw`
  SELECT * FROM "Post" WHERE published = true
`

Transactions

// Multiple operations as transaction
const [user, post] = await prisma.$transaction([
  prisma.user.create({
    data: { email: 'new@example.com' }
  }),
  prisma.post.create({
    data: {
      title: 'Post',
      authorId: userId
    }
  })
])

// Or with callback
await prisma.$transaction(async (tx) => {
  const user = await tx.user.update({
    where: { id: userId },
    data: { name: 'Updated' }
  })

  await tx.post.update({
    where: { id: postId },
    data: { authorId: user.id }
  })
})

Middleware & Hooks

// prisma/schema.prisma - Add timestamps
model Post {
  // ... other fields
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

// Use in queries
const recentPosts = await prisma.post.findMany({
  where: {
    createdAt: {
      gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
    }
  }
})

Real-World Example: Blog

// lib/db.ts
import { prisma } from './prisma'

export async function getPosts(page = 1, limit = 10) {
  const skip = (page - 1) * limit

  const [posts, total] = await Promise.all([
    prisma.post.findMany({
      where: { published: true },
      include: { author: { select: { id: true, name: true } } },
      orderBy: { createdAt: 'desc' },
      skip,
      take: limit
    }),
    prisma.post.count({ where: { published: true } })
  ])

  return { posts, total, pages: Math.ceil(total / limit) }
}

export async function getPostBySlug(slug: string) {
  return prisma.post.findUnique({
    where: { slug },
    include: {
      author: true,
      comments: {
        include: { author: true },
        orderBy: { createdAt: 'desc' }
      }
    }
  })
}

export async function createPost(data: PostData, authorId: number) {
  return prisma.post.create({
    data: { ...data, authorId },
    include: { author: true }
  })
}

FAQ

Q: How do I handle Prisma type safety in TypeScript? A: Prisma generates types automatically. Import from @prisma/client for full type safety.

Q: Should I use raw SQL or Prisma queries? A: Use Prisma queries for safety. Raw SQL only when necessary and properly parameterized.

Q: How do I optimize slow queries? A: Use include/select to fetch only needed fields, add database indexes, and use skip/take for pagination.


Prisma transforms database interactions into type-safe, productive development.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro