Getting started with Prisma on Node.js and PostgreSQL
Install Prisma in a Node.js project, define a PostgreSQL schema, run the first migration, generate the TypeScript client and make first reads and writes.
This article is published in English.
Sources: Getting Started with Prisma: Quick and Easy Node.js Setup
Introduction
Prisma is a practical ORM choice for Node and TypeScript services that need typed database access. The walkthrough below is a fast path from a basic Node project to migrations, a generated client, and a few read/write queries against PostgreSQL. Prisma sits between application TypeScript and PostgreSQL: the schema file is the contract, migrations move the database, and the generated client gives typed methods for each model. Skipping any of those three usually shows up later as either runtime SQL errors or TypeScript that cannot see the fields the database already has.
Have a Node project ready first—and TypeScript if that is the project language.
Let’s set up Prisma
Step 1: Install Prisma and Prisma Client
Install both packages in the project directory:
npm install prisma @prisma/client
prismais the CLI for schema work, migrations, and client generation.@prisma/clientis the library application code imports to query the database.
Step 2: Initialize Prisma
Initialize with PostgreSQL and a custom client output path:
npx prisma init --datasource-provider postgresql --output ../generated/prisma
What this command does:
- Creates a
prismadirectory withschema.prismafor connection settings and models. - Sets the datasource to PostgreSQL.
- Creates a root
.envfile for secrets such as the database URL. --output ../generated/prismaredirects generated client output away from the default location.
Note: prisma init defaults to PostgreSQL. For another engine—or when --datasource-provider was omitted—edit the datasource block in schema.prisma.
PostgreSQL example:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
Put the connection string in .env:
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA"
Replace USER, PASSWORD, HOST, PORT, DATABASE, and SCHEMA with real values before modeling or migrating.
Step 3: Add Prisma schema and run migrations
Use Prisma Migrate to create tables. Add a starter model to prisma/schema.prisma:
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String @db.VarChar(255)
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
model Profile {
id Int @id @default(autoincrement())
bio String?
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
profile Profile?
}
For deeper modeling guidance, see the official data model docs. For this setup path, the sample model is enough to run a first migration.
Create and apply the migration:
npx prisma migrate dev --name init
This will:
- generate SQL from the schema
- apply it to the database
- refresh the client so models are usable in code
--name init is only a label. Useful names later might be add-post-model or add-user-profile.
Step 4: Generate Prisma Client
Generate the client after install:
npx prisma generate
Generation reads schema.prisma and writes TypeScript types and query methods under generated/prisma (or the configured output). Import the constructor and create an instance:
import { PrismaClient } from './generated/prisma'
const prisma = new PrismaClient()
That instance can read and write through models such as User, Post, and Profile.
Good to know:
When prisma generate runs, it emits code tailored to the current schema. After schema edits, regenerate. Keeping the database aligned usually means prisma migrate dev or prisma db push; those commands also regenerate the client under the hood.
Step 5: Querying the database using TypeScript and PostgreSQL
With a generated client, start with a small script.
1. Create a new file named index.ts:
import { PrismaClient } from './generated/prisma'
const prisma = new PrismaClient()
async function main() {
// ... you will write your Prisma Client queries here
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
2. Reading data — list users inside main:
async function main() {
// ... you will write your Prisma Client queries here
const allUsers = await prisma.user.findMany()
console.log(allUsers)
}
3. Writing data — create related records:
async function main() {
await prisma.user.create({
data: {
name: 'Alice',
email: 'alice@prisma.io',
posts: {
create: { title: 'Hello World' },
},
profile: {
create: { bio: 'I like turtles' },
},
},
})
const allUsers = await prisma.user.findMany({
include: {
posts: true,
profile: true,
},
})
console.dir(allUsers, { depth: null })
}
prisma.user.createinserts a user with related post and profile data.findManywithincludeloads related posts and profile.console.dirwith full depth prints nested objects clearly.
Treat generation as part of the edit loop, not a one-time bootstrap. Schema changes without regeneration leave application code talking to yesterday’s types. Migrations without a matching client leave runtime and compile-time views of the database out of sync. A simple habit—migrate (or push), then confirm the client output updated—prevents a large class of “why doesn’t TypeScript know about this field?” moments.
For local development, keep .env out of version control and document the expected DATABASE_URL shape in the README. For CI, inject the same variable from secrets and run migrate deploy (or an equivalent gated step) before integration tests that touch the database. The setup above is enough to learn the client; production hardening adds connection pooling, a shared client singleton, and explicit disconnect on process shutdown.
What’s next?
That covers a TypeScript Node setup with Prisma: install, initialize, migrate, generate, and run basic reads and writes. The next depth layer is data modeling—relationships, constraints, and advanced schema features that keep the database layer expressive as the app grows.