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 init

prisma 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 generate

Create 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.