Prisma
Use Prisma ORM for type-safe database access. Because NukeJS pages and API routes run on the server, Prisma works exactly as you'd expect.
Integration
Install
terminalbash
npm install prisma @prisma/client
npx prisma initprisma init creates a prisma/schema.prisma file and a .env with a DATABASE_URL placeholder.
Define your schema
prisma/schema.prismatypescript
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?
createdAt DateTime @default(now())
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())
}Generate the client and migrate
terminalbash
npx prisma migrate dev --name init
npx prisma generateCreate a singleton client
In development, hot reloading can create multiple Prisma Client instances. A singleton pattern prevents this:
lib/db.tstypescript
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({ log: ['query'] })
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}Query from a server page
Server pages can import and use Prisma directly — no API round-trip needed:
app/pages/blog/index.tsxtypescript
import { prisma } from '../../lib/db'
export default async function BlogIndex() {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
include: { author: true },
take: 10,
})
return (
<main>
<h1>Blog</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>By {post.author.name} · {post.createdAt.toLocaleDateString()}</p>
</article>
))}
</main>
)
}Use Prisma in API routes
Full CRUD via API routes — useful for client components that need to mutate data:
server/posts/index.tstypescript
import type { IncomingMessage, ServerResponse } from 'http'
import { prisma } from '../../lib/db'
export async function GET(req: IncomingMessage, res: ServerResponse) {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
})
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(posts))
}
export async function POST(req: IncomingMessage & { body: any }, res: ServerResponse) {
const { title, content, authorId } = req.body
const post = await prisma.post.create({
data: { title, content, authorId },
})
res.writeHead(201, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(post))
}server/posts/[id].tstypescript
import type { IncomingMessage, ServerResponse } from 'http'
import { prisma } from '../../lib/db'
export async function GET(req: IncomingMessage & { params: any }, res: ServerResponse) {
const id = parseInt(req.params.id as string)
const post = await prisma.post.findUnique({ where: { id } })
if (!post) {
res.writeHead(404, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'Not found' }))
return
}
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(post))
}
export async function PATCH(req: IncomingMessage & { params: any; body: any }, res: ServerResponse) {
const id = parseInt(req.params.id as string)
const post = await prisma.post.update({
where: { id },
data: req.body,
})
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(post))
}
export async function DELETE(req: IncomingMessage & { params: any }, res: ServerResponse) {
const id = parseInt(req.params.id as string)
await prisma.post.delete({ where: { id } })
res.writeHead(204)
res.end()
}Client component calling the API
app/components/PublishButton.tsxtypescript
"use client"
import { useState } from 'react'
export default function PublishButton({ postId }: { postId: number }) {
const [published, setPublished] = useState(false)
const [loading, setLoading] = useState(false)
async function publish() {
setLoading(true)
await fetch(`/posts/${postId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ published: true }),
})
setPublished(true)
setLoading(false)
}
return (
<button onClick={publish} disabled={loading || published}>
{published ? '✅ Published' : loading ? 'Publishing…' : 'Publish'}
</button>
)
}Add prisma/schema.prisma to .gitignore?No — commit your schema. Add
.env to .gitignore so DATABASE_URL stays secret. Use environment variables in your deployment platform for production credentials.