Back to blog
nodejsintermediate

Node.js Database Integration with Prisma

Connect a Node.js/Express API to PostgreSQL using Prisma ORM — schema definition, migrations, CRUD queries, relations, and connection pooling.

LearnixoApril 16, 20267 min read
Node.jsPrismaPostgreSQLORMDatabaseIntermediate
Share:𝕏

Prisma is the most popular ORM for Node.js. It gives you a type-safe database client, auto-generated from your schema, with migrations built in. No raw SQL required for standard CRUD.


Install Prisma

Bash
npm install prisma @prisma/client
npx prisma init --datasource-provider postgresql

This creates:

  • prisma/schema.prisma — your database schema
  • .env with DATABASE_URL

Update .env:

DATABASE_URL="postgresql://username:password@localhost:5432/mydb?schema=public"

Define Your Schema

prisma/schema.prisma

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
  password  String
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  posts     Post[]
  orders    Order[]
}

model Post {
  id          Int      @id @default(autoincrement())
  title       String
  content     String?
  published   Boolean  @default(false)
  createdAt   DateTime @default(now())

  author      User     @relation(fields: [authorId], references: [id])
  authorId    Int

  tags        Tag[]
}

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

model Order {
  id        Int         @id @default(autoincrement())
  total     Decimal
  status    OrderStatus @default(PENDING)
  createdAt DateTime    @default(now())

  user      User        @relation(fields: [userId], references: [id])
  userId    Int

  items     OrderItem[]
}

model OrderItem {
  id        Int   @id @default(autoincrement())
  quantity  Int
  price     Decimal

  order     Order @relation(fields: [orderId], references: [id])
  orderId   Int
}

enum Role {
  USER
  ADMIN
}

enum OrderStatus {
  PENDING
  PROCESSING
  SHIPPED
  DELIVERED
  CANCELLED
}

Run Migrations

Bash
# Create and apply a migration
npx prisma migrate dev --name init

# Apply migrations in production (no dev data reset)
npx prisma migrate deploy

# View current database state
npx prisma studio   # opens a browser GUI

After changing the schema, always run prisma migrate dev to update the database and regenerate the Prisma client.


Prisma Client Setup

Create a singleton to avoid multiple connections:

src/lib/prisma.ts

TYPESCRIPT
import { PrismaClient } from '@prisma/client';

declare global {
  // eslint-disable-next-line no-var
  var prisma: PrismaClient | undefined;
}

export const prisma = global.prisma ?? new PrismaClient({
  log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
});

if (process.env.NODE_ENV !== 'production') {
  global.prisma = prisma;  // prevent hot-reload from creating new connections
}

CRUD Operations

Create

TYPESCRIPT
import { prisma } from '../lib/prisma';

// Create one
const user = await prisma.user.create({
  data: {
    email: 'alice@example.com',
    name:  'Alice',
    password: hashedPassword,
  },
});

// Create with relation
const post = await prisma.post.create({
  data: {
    title:    'My First Post',
    content:  'Hello world',
    author:   { connect: { id: userId } },
    // OR: authorId: userId
  },
  include: { author: true },  // return author data
});

// Create many
await prisma.tag.createMany({
  data: [
    { name: 'typescript' },
    { name: 'nodejs' },
  ],
  skipDuplicates: true,
});

Read

TYPESCRIPT
// Find one by unique field
const user = await prisma.user.findUnique({
  where: { email: 'alice@example.com' },
  select: { id: true, name: true, email: true },  // only fetch these fields
});

// Find first matching
const post = await prisma.post.findFirst({
  where: { published: true, authorId: userId },
  orderBy: { createdAt: 'desc' },
});

// Find many with filters, pagination, relations
const posts = await prisma.post.findMany({
  where: {
    published: true,
    title: { contains: searchTerm, mode: 'insensitive' },
  },
  include: {
    author: { select: { name: true, email: true } },
    tags:   true,
  },
  orderBy:  { createdAt: 'desc' },
  skip:     (page - 1) * pageSize,
  take:     pageSize,
});

// Count
const total = await prisma.post.count({
  where: { published: true },
});

Update

TYPESCRIPT
// Update one
const updated = await prisma.user.update({
  where: { id: userId },
  data:  { name: 'Alice Smith' },
});

// Update many
const { count } = await prisma.post.updateMany({
  where: { authorId: userId },
  data:  { published: false },
});

// Upsert — create if not exists, update if exists
const tag = await prisma.tag.upsert({
  where:  { name: 'typescript' },
  update: {},
  create: { name: 'typescript' },
});

Delete

TYPESCRIPT
// Delete one
await prisma.user.delete({ where: { id: userId } });

// Delete many
const { count } = await prisma.post.deleteMany({
  where: { published: false, createdAt: { lt: thirtyDaysAgo } },
});

Transactions

TYPESCRIPT
// $transaction — all succeed or all rollback
const [user, post] = await prisma.$transaction([
  prisma.user.create({ data: { email: 'bob@example.com', name: 'Bob', password: '' } }),
  prisma.post.create({ data: { title: 'Welcome', authorId: 1 } }),
]);

// Interactive transaction (for conditional logic)
const result = await prisma.$transaction(async (tx) => {
  const order = await tx.order.create({
    data: { userId, total: 99.99, status: 'PROCESSING' },
  });

  // Check inventory, deduct stock, etc.
  await tx.orderItem.createMany({ data: items.map(i => ({ ...i, orderId: order.id })) });

  // If anything throws here, the whole transaction rolls back
  return order;
});

Filtering — Common Patterns

TYPESCRIPT
// String filters
where: { name: { contains: 'ali', mode: 'insensitive' } }
where: { name: { startsWith: 'Al' } }
where: { name: { endsWith: 'ce' } }

// Number filters
where: { price: { gte: 10, lte: 100 } }
where: { stock: { gt: 0 } }

// Date filters
where: { createdAt: { gte: new Date('2026-01-01') } }

// IN / NOT IN
where: { status: { in: ['PENDING', 'PROCESSING'] } }
where: { id: { notIn: [1, 2, 3] } }

// Relation filters
where: { author: { email: 'alice@example.com' } }
where: { posts: { some: { published: true } } }
where: { posts: { every: { published: true } } }
where: { posts: { none: { published: false } } }

// AND / OR / NOT
where: {
  AND: [
    { status: 'ACTIVE' },
    { OR: [{ role: 'ADMIN' }, { email: { endsWith: '@company.com' } }] }
  ]
}

Service Layer with Prisma

TYPESCRIPT
import { prisma } from '../lib/prisma';
import { Prisma } from '@prisma/client';

export class UserService {
  async findAll(page: number, pageSize: number) {
    const [users, total] = await prisma.$transaction([
      prisma.user.findMany({
        skip: (page - 1) * pageSize,
        take: pageSize,
        select: { id: true, name: true, email: true, role: true, createdAt: true },
        orderBy: { createdAt: 'desc' },
      }),
      prisma.user.count(),
    ]);
    return { data: users, total, page, pageSize };
  }

  async findById(id: number) {
    return prisma.user.findUnique({
      where: { id },
      include: { orders: { take: 5, orderBy: { createdAt: 'desc' } } },
    });
  }

  async create(data: Prisma.UserCreateInput) {
    return prisma.user.create({ data });
  }

  async update(id: number, data: Prisma.UserUpdateInput) {
    try {
      return await prisma.user.update({ where: { id }, data });
    } catch (e) {
      if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2025') {
        return null;  // record not found
      }
      throw e;
    }
  }

  async delete(id: number): Promise<boolean> {
    try {
      await prisma.user.delete({ where: { id } });
      return true;
    } catch (e) {
      if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2025') {
        return false;
      }
      throw e;
    }
  }
}

Quick Reference

Install:         npm install prisma @prisma/client
Init:            npx prisma init --datasource-provider postgresql
Migrate:         npx prisma migrate dev --name description
Studio:          npx prisma studio
Client:          new PrismaClient() — use singleton
Create:          prisma.model.create({ data: {} })
Read:            prisma.model.findUnique/findFirst/findMany
Update:          prisma.model.update({ where: {}, data: {} })
Delete:          prisma.model.delete({ where: {} })
Pagination:      skip: (page-1)*size, take: size
Include:         include: { relation: true }
Select:          select: { field: true }
Transaction:     prisma.$transaction([...]) or prisma.$transaction(async tx => {})
Error P2025:     Record not found

Enjoyed this article?

Explore the learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.