Lately, I’ve been thinking a lot about how to build web applications faster without sacrificing quality or performance. It seems like every project requires a robust frontend, a reliable backend, and a database that just works. That’s why the combination of Next.js and Prisma has become such a central part of my workflow. It brings together the best of both worlds, and today I want to share how you can use it to streamline your own development process.
When you start a new Next.js project, adding Prisma is straightforward. First, install the Prisma CLI and initialize it in your project. This creates the initial setup files you need.
npm install prisma --save-dev
npx prisma init
This command generates a prisma
directory with a schema.prisma
file. Here, you define your data model. Let’s say you’re building a blog. Your schema might include a simple Post
model.
// prisma/schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
createdAt DateTime @default(now())
}
After defining your schema, you generate the Prisma Client, which provides type-safe database access.
npx prisma generate
Now, how do you actually use this in Next.js? One of the strengths of Next.js is its API routes. You can create an endpoint to fetch all published posts. Notice how the types generated by Prisma make this code both safe and clear.
// pages/api/posts/index.ts
import { NextApiRequest, NextApiResponse } from 'next'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'GET') {
const posts = await prisma.post.findMany({
where: { published: true },
})
res.status(200).json(posts)
} else {
res.setHeader('Allow', ['GET'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}
But what about using this data on the frontend? Next.js makes data fetching simple. You can use getServerSideProps
to pre-render a page with these posts. The best part? Full type safety from the database all the way to your component.
// pages/index.tsx
import { GetServerSideProps } from 'next'
import { PrismaClient, Post } from '@prisma/client'
const prisma = new PrismaClient()
export const getServerSideProps: GetServerSideProps = async () => {
const posts: Post[] = await prisma.post.findMany({
where: { published: true },
})
return { props: { posts } }
}
const HomePage = ({ posts }: { posts: Post[] }) => {
return (
<div>
<h1>Published Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
}
export default HomePage
Have you ever wondered how you can keep your database queries efficient as your app grows? Prisma’s query engine is optimized for performance, and when paired with Next.js caching and static generation, your app can handle scale gracefully. For instance, generating static pages for each blog post is a common pattern.
// pages/posts/[id].tsx
import { GetStaticPaths, GetStaticProps } from 'next'
import { PrismaClient, Post } from '@prisma/client'
const prisma = new PrismaClient()
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await prisma.post.findMany({
where: { published: true },
select: { id: true },
})
const paths = posts.map((post) => ({ params: { id: post.id.toString() } }))
return { paths, fallback: 'blocking' }
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const post = await prisma.post.findUnique({
where: { id: Number(params?.id) },
})
if (!post) {
return { notFound: true }
}
return { props: { post } }
}
const PostPage = ({ post }: { post: Post }) => {
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
export default PostPage
This setup not only ensures great performance but also improves SEO, as content is pre-rendered at build time. And with Prisma managing your database schema, making changes is less stressful. You modify your schema.prisma
, create a migration, and apply it.
npx prisma migrate dev --name add_author_field
The integration feels natural. You write your schema, Prisma gives you types, and Next.js provides the structure to build your entire application. It reduces the back-and-forth between different parts of your codebase and lets you focus on creating features.
What if you need real-time data? While this setup excels in static and server-rendered scenarios, you can complement it with client-side fetching for dynamic content. The consistency in types across the stack prevents many common errors.
I’ve found that using Next.js with Prisma changes how I approach full-stack projects. It turns complex database interactions into a more manageable and enjoyable process. The feedback loop is tight, and the confidence that type safety provides is invaluable.
If you’ve been looking for a way to simplify your full-stack development, I highly recommend giving this combination a try. It might just become your new default setup.
What has your experience been with combining frontend frameworks and database tools? I’d love to hear your thoughts—feel free to share this article and leave a comment below.