Getting Started with Prisma ORM

Database operations remain a cornerstone of modern web development. While traditional Object-Relational Mapping (ORM) tools have long helped developers bypass raw SQL, they frequently introduce friction through fragile type safety, tedious configuration, and complex migration workflows. Standing out as a modern alternative, Prisma has rapidly emerged as a favorite database toolkit for Node.js and TypeScript developers, driven by its declarative data modeling, auto-generated type-safe client, and intuitive migration utilities.

This guide takes you from the ground up, walking you through the basics of Prisma ORM and helping you implement standard Create, Read, Update, and Delete (CRUD) operations in a TypeScript project.

Prisma is built around three core components:

  1. Prisma Client: An auto-generated, type-safe query builder tailored for Node.js and TypeScript.
  2. Prisma Migrate: A declarative migration system that keeps your database schema in sync with your models.
  3. Prisma Studio: A visual dashboard to browse and edit your database contents directly.

At the heart of Prisma lies the schema.prisma file, where you define your database models using an intuitive Domain-Specific Language (DSL) while letting Prisma handle the underlying SQL complexity.
Before diving in, make sure you have Node.js installed (version 16.x or higher is recommended). We will build a new TypeScript project to demonstrate how Prisma works.

First, initialize a new directory and set up your package dependencies:

mkdir prisma-quickstart
cd prisma-quickstart
npm init -y
npm install typescript ts-node @types/node --save-dev

Next, generate the TypeScript configuration file:

npx tsc --init

Now, install the Prisma CLI as a development dependency and initialize Prisma in your workspace:

npm install prisma --save-dev
npx prisma init

Running the init command creates a prisma folder containing your schema.prisma file, alongside a .env file at the root level for managing environment variables like database connection strings.

Defining Your Data Model

Open the prisma/schema.prisma file to view the default setup. For this guide, we will use SQLite for a frictionless, zero-setup experience, and define two models: User and Post.

Update your schema.prisma file to match the configuration below:

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

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

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

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  authorId  Int
  author    User    @relation(fields: [authorId], references: [id])
}

This model establishes a standard one-to-many relationship: a single user can author multiple posts, and each post is tied to exactly one user.

Running Database Migrations

With your data model specified, you need to propagate these changes to your database using Prisma Migrate. Run the following command:

npx prisma migrate dev --name init

This command performs two vital actions:

  1. It applies the schema changes to create the appropriate tables in your SQLite database.
  2. It generates the tailored Prisma Client code matching your current models.

You will see confirmation logs in your terminal once the migration finishes successfully.

Performing CRUD Operations with Prisma Client

Now you can write application logic to interact with your database. First, install the runtime dependency for Prisma Client:

npm install @prisma/client

Create an index.ts file in your root directory and add the following script to test out creating, reading, updating, and deleting records:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // 1. Create a user and nested posts
  const newUser = await prisma.user.create({
    data: {
      email: 'alice@example.com',
      name: 'Alice',
      posts: {
        create: [
          { title: 'Quick Start with Prisma', content: 'Learning how to use Prisma ORM...' },
          { title: 'TypeScript Best Practices', content: 'Building robust apps with type safety...' },
        ],
      },
    },
  })
  console.log('Created user:', newUser)

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

  // 3. Update an existing record
  const updatedUser = await prisma.user.update({
    where: { email: 'alice@example.com' },
    data: { name: 'Alice Smith' },
  })
  console.log('Updated user:', updatedUser)

  // 4. Delete records
  // Note: Due to foreign key constraints, we delete child records or handle cascading rules first
  const deletedPosts = prisma.post.deleteMany({
    where: { author: { email: 'alice@example.com' } },
  })
  const deletedUser = prisma.user.delete({
    where: { email: 'alice@example.com' },
  })

  await prisma.$transaction([deletedPosts, deletedUser])
  console.log('User and associated posts have been deleted.')
}

main()
  .catch((e) => {
    console.error(e)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })

Execute the script using ts-node:

npx ts-node index.ts

Your console will output the corresponding results for each database action.

Visualizing Data with Prisma Studio

Beyond programmatic queries, Prisma ships with a gorgeous built-in visual interface called Prisma Studio. Simply run this command in your terminal:

npx prisma studio

Your browser will automatically launch a management dashboard at http://localhost:5555, allowing you to inspect, filter, create, and modify database entries visually.

Conclusion

By following this guide, you have learned the core workflow of Prisma ORM:

  1. Declaring data models inside schema.prisma.
  2. Managing structural database changes via prisma migrate.
  3. Executing type-safe CRUD logic through Prisma Client.

Prisma drastically cuts down boilerplate while leveraging TypeScript to make interacting with databases smoother and safer than ever. Feel free to dive deeper into advanced features like middleware, complex filtering, and aggregation queries as you scale this into a production-ready application.