Prisma Complete Beginner Guide 2026 (First Model + Query)

Prisma is the most-used TypeScript ORM in 2026 with roughly 5 million weekly npm downloads. If you are building a Next.js or Node.js app that talks to a database, Prisma turns SQL tables into typed TypeScript objects, generates migrations automatically, and gives you an autocomplete-friendly query API. This guide walks you from a blank folder to a working Prisma project with a real model, migration, and typed queries. No prior ORM experience required.

Quick 2026 verdict

Prisma is beginner-friendly because its schema DSL reads like plain text, autocomplete shows every relation as you type, and prisma migrate handles SQL for you. Best choice in 2026 for developers new to ORMs, especially on Next.js server-side setups. If you deploy to serverless or edge, expect cold-start friction and read the Drizzle comparison too.

Prerequisites

  • Node.js 20 or newer
  • Basic TypeScript familiarity (variable types, interfaces)
  • A database: PostgreSQL, MySQL, or SQLite. SQLite is fine for learning and requires no server.
  • A text editor: VS Code recommended (Prisma has an official extension)

Install Prisma

mkdir my-prisma-app
cd my-prisma-app
npm init -y

npm install -D prisma typescript ts-node @types/node
npm install @prisma/client

npx prisma init --datasource-provider sqlite

The prisma init command creates two files: prisma/schema.prisma (the schema DSL) and .env (for the DATABASE_URL environment variable). SQLite uses a local file, so the default URL points to ./prisma/dev.db and requires no separate server.

Anatomy of a Prisma schema

Open prisma/schema.prisma. It starts with two blocks:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

The generator block tells Prisma what client code to produce (usually the JavaScript / TypeScript client). The datasource block declares your database type and where to find the connection string.

Define your first model

Add a User model at the bottom of the schema file:

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  posts     Post[]
}

model Post {
  id        String   @id @default(cuid())
  title     String
  body      String
  published Boolean  @default(false)
  authorId  String
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
}

Notes on the syntax:

  • @id marks the primary key
  • @default(cuid()) auto-generates a unique ID (cuid is safer than uuid for URL-friendliness)
  • @unique creates a unique index
  • String? means nullable (email is required, name is optional)
  • User and Post have a one-to-many relationship: one User has many Posts
  • @relation(fields: [authorId], references: [id]) tells Prisma the foreign key setup

Run your first migration

npx prisma migrate dev --name init

Prisma does three things: creates the SQL migration file in prisma/migrations/, applies it to your SQLite database (creating User and Post tables), and generates the Prisma Client with typed access to those tables. If you look at the terminal output, you will see the raw SQL Prisma produced.

Write your first queries

Create src/index.ts:

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

const prisma = new PrismaClient();

async function main() {
  // CREATE a user
  const user = await prisma.user.create({
    data: {
      email: '[email protected]',
      name: 'Alice',
    },
  });
  console.log('Created user:', user);

  // CREATE a post for that user
  const post = await prisma.post.create({
    data: {
      title: 'Hello Prisma',
      body: 'This is my first Prisma post.',
      authorId: user.id,
      published: true,
    },
  });
  console.log('Created post:', post);

  // READ all users with their posts
  const users = await prisma.user.findMany({
    include: { posts: true },
  });
  console.log('All users with posts:', JSON.stringify(users, null, 2));

  // UPDATE a post
  const updated = await prisma.post.update({
    where: { id: post.id },
    data: { title: 'Hello Prisma (edited)' },
  });
  console.log('Updated post:', updated);

  // DELETE a post
  await prisma.post.delete({ where: { id: post.id } });
  console.log('Post deleted');
}

main()
  .catch((e) => console.error(e))
  .finally(async () => await prisma.$disconnect());

Run it:

npx ts-node src/index.ts

You just wrote CRUD (Create, Read, Update, Delete) in typed TypeScript with no SQL. Notice that prisma.user.create(), prisma.post.findMany(), and so on are all fully typed based on your schema. Autocomplete works. If you rename title to headline in the schema and re-run prisma migrate dev, TypeScript will flag every place you still reference title.

Explore data with Prisma Studio

npx prisma studio

Prisma Studio opens a browser-based table editor at http://localhost:5555 for exploring your data. You can browse rows, filter, edit values, and add new records without writing SQL. This is the fastest way to inspect what your code did during development.

Common queries you will use often

// Find by unique field
await prisma.user.findUnique({ where: { email: '[email protected]' } });

// Find with a filter
await prisma.post.findMany({
  where: { published: true, author: { name: 'Alice' } },
  orderBy: { createdAt: 'desc' },
  take: 10,
});

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

// Upsert (insert or update if exists)
await prisma.user.upsert({
  where: { email: '[email protected]' },
  create: { email: '[email protected]', name: 'Bob' },
  update: { name: 'Bob (updated)' },
});

// Transactions
await prisma.$transaction([
  prisma.user.create({ data: { email: '[email protected]' } }),
  prisma.post.create({ data: { title: 'Hi', body: '', authorId: '...' } }),
]);

// Raw SQL when Prisma cannot express it
const results = await prisma.$queryRaw`SELECT COUNT(*) FROM Post WHERE published = ${true}`;

Switch to PostgreSQL for production

When you deploy, most projects switch from SQLite to PostgreSQL. In schema.prisma:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Update .env:

DATABASE_URL="postgresql://user:pass@localhost:5432/mydb?schema=public"

Run npx prisma migrate dev --name switch_to_postgres. Prisma recreates the migration for Postgres syntax. Existing SQLite data is not migrated automatically; you handle that separately (dump SQLite, transform to Postgres, load).

Common beginner gotchas

  • Forgetting to run prisma generate after schema changes. This is called automatically by prisma migrate, but if you edit the schema without migrating (which you should not do in production), the client stays outdated. Run npx prisma generate manually if in doubt.
  • Nullable vs required. String is required. String? is nullable. Reviewers and IDEs will error on mismatch, so pay attention when adding new columns.
  • Relations without foreign keys. author User @relation(fields: [authorId], references: [id]) requires the authorId String field on the same model. Prisma cannot infer the foreign key column without it.
  • Reused PrismaClient in dev. In Next.js dev mode, hot reload creates new PrismaClient instances on every file change, which exhausts DB connections. Use a global singleton pattern in lib/prisma.ts.
  • Ignoring migration files in Git. Migration files under prisma/migrations/ must be committed. They are your database history. Do not gitignore them.
  • Using db push in production. prisma db push is for prototyping; it can drop columns silently. Always use prisma migrate for anything shared.

Frequently Asked Questions

Do I need to know SQL to use Prisma?

Basic SQL helps but is not required. Prisma’s query API is high-level: findMany, create, update. For 90% of queries you never touch SQL. When you do need it, prisma.$queryRaw is available.

Can I use Prisma with an existing database?

Yes. Run npx prisma db pull to introspect an existing database and generate a schema.prisma from it. Then npx prisma generate gives you the typed client. You can continue with migrations from that point.

Does Prisma work with Next.js server actions?

Yes, natively. Import the Prisma client in a Server Action, run queries, return data. Just make sure your Prisma client is a singleton (import from lib/prisma.ts) to avoid connection exhaustion in dev.

How does Prisma handle migrations in production?

Use npx prisma migrate deploy in your production release pipeline. This applies any pending migrations without creating new ones. Never use prisma migrate dev or prisma db push in production; both can lead to data loss.

Is Prisma good for serverless (Vercel, AWS Lambda)?

Prisma works but has cold-start issues due to its Rust query engine. Use Prisma Accelerate or the Data Proxy for smooth serverless. Alternatively, consider Drizzle ORM if bundle size and cold starts are critical for your deployment.

How do I add validation on top of Prisma?

Prisma validates types (String, Int, etc.) but not business rules (email format, password strength). Layer Zod on top: validate incoming data with Zod, then pass validated data to Prisma. This is the standard 2026 Next.js pattern.

Related Modern Web Dev tutorials

  • Prisma vs Drizzle vs Kysely 2026 (TypeScript ORM Comparison)
  • Next.js 15 Complete Beginner Guide 2026 (First App)
  • TypeScript Complete Beginner Tutorial 2026
  • Supabase vs Firebase 2026 (Backend-as-a-Service Comparison)

Official documentation

Leave a Comment