diff --git a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/index.mdx b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/index.mdx
index ac1a42c5d0..5db91bc226 100644
--- a/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/index.mdx
+++ b/apps/blog/content/blog/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/index.mdx
@@ -1,11 +1,12 @@
---
-title: "Backend with TypeScript PostgreSQL & Prisma: Data Modeling & CRUD"
+title: "Backend with TypeScript, PostgreSQL & Prisma: Data Modeling & CRUD"
slug: "backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1"
date: "2020-07-30"
+updatedAt: "2026-07-03"
authors:
- "Daniel Norman"
metaTitle: "TypeScript, PostgreSQL, Prisma | Data Modeling, CRUD, Aggregates"
-metaDescription: "Learn how to model data, perform CRUD operations, and query aggregates in a TypeScript and PostgreSQL backend built with Prisma."
+metaDescription: "Learn to design a PostgreSQL data model and run type-safe CRUD and aggregate queries with Prisma ORM 7 and TypeScript. Updated for the rust-free Prisma Client."
metaImagePath: "/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/imgs/hero-df67ee11927bde800c1ba033b94e182ab3565110-1692x852.png"
heroImagePath: "/backend-prisma-typescript-orm-with-postgresql-data-modeling-tsjs1ps7kip1/imgs/hero-df67ee11927bde800c1ba033b94e182ab3565110-1692x852.png"
heroImageAlt: "Backend with TypeScript PostgreSQL & Prisma: Data Modeling & CRUD"
@@ -13,19 +14,17 @@ series: backend-prisma-typescript-orm-with-postgresql
seriesIndex: 1
---
-This article is part of a series of [live streams](https://www.youtube.com/playlist?list=PLn2e1F9Rfr6k7ULe0gzQvtaXLoXrPqpki) and articles on building a backend with TypeScript, PostgreSQL, and Prisma. In this article, which summarizes the first live stream, we'll look at how to design the data model, perform CRUD operations, and query aggregates using Prisma.
+> **Update (July 2026):** This article was updated for Prisma ORM 7. It uses the rust-free `prisma-client` generator, the `prisma.config.ts` config file, and driver adapters. All commands and code work verbatim on Prisma ORM 7 with Node.js 20 or later.
-
+This article is part of a series on building a backend with TypeScript, PostgreSQL, and Prisma. In this first part, you will design a data model, apply it to your database with Prisma Migrate, and perform CRUD and aggregate queries with Prisma Client.
## Introduction
-The goal of the series is to explore and demonstrate different patterns, problems, and architectures for a modern backend by solving a concrete problem: **a grading system for online courses.** This is a good example because it features diverse relations types and is complex enough to represent a real-world use-case.
+The goal of the series is to explore and demonstrate different patterns, problems, and architectures for a modern backend by solving a concrete problem: **a grading system for online courses.** This is a good example because it features diverse relation types and is complex enough to represent a real-world use case.
-The recording of the live stream is available above and covers the same ground as this article.
+### What the series covers
-### What the series will cover
-
-The series will focus on the role of the database in every aspect of backend development covering:
+The series focuses on the role of the database in every aspect of backend development, covering:
- Data modeling
- CRUD
@@ -40,15 +39,13 @@ The series will focus on the role of the database in every aspect of backend dev
### What you will learn today
-This first article of the series will begin by laying out the problem domain and developing the following aspects of the backend:
+This first article lays out the problem domain and develops the following aspects of the backend:
1. **Data modeling:** Mapping the problem domain to a database schema
-2. **CRUD:** Implement Create, Read, Update, and Delete queries with [Prisma Client](https://www.prisma.io/docs/concepts/components/prisma-client) against the database
-3. **Aggregation:** Implement aggregate queries with Prisma to calculate averages, etc.
-
-By the end of this article you will have a Prisma schema, a corresponding database schema created by Prisma Migrate, and a seed script which uses Prisma Client to perform CRUD and aggregation queries.
+2. **CRUD:** Implementing Create, Read, Update, and Delete queries with [Prisma Client](https://www.prisma.io/docs/orm/prisma-client)
+3. **Aggregation:** Implementing aggregate queries with Prisma Client to calculate averages and more
-The next parts of this series will cover the other aspects from the list in detail.
+By the end of this article you will have a Prisma schema, a corresponding database schema created by Prisma Migrate, and a seed script that uses Prisma Client to perform CRUD and aggregation queries.
> **Note:** Throughout the guide you'll find various **checkpoints** that enable you to validate whether you performed the steps correctly.
@@ -56,55 +53,179 @@ The next parts of this series will cover the other aspects from the list in deta
### Assumed knowledge
-This series assumes basic knowledge of TypeScript, Node.js, and relational databases. If you're experienced with JavaScript but haven't had the chance to try TypeScript, you should still be able to follow along. The series will use PostgreSQL, however, most of the concepts apply to other relational databases such as MySQL. Beyond that, no prior knowledge of Prisma is required as that will be covered in the series.
+This series assumes basic knowledge of TypeScript, Node.js, and relational databases. If you're experienced with JavaScript but haven't had the chance to try TypeScript, you should still be able to follow along. The series uses PostgreSQL, however, most of the concepts apply to other relational databases such as MySQL. Beyond that, no prior knowledge of Prisma is required.
### Development environment
You should have the following installed:
-- [Node.js](https://nodejs.org/en/)
-- [Docker](https://www.docker.com/) (will be used to run a development PostgreSQL database)
+- [Node.js](https://nodejs.org/en/) 20 or later
+
+If you're using Visual Studio Code, the [Prisma extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) is recommended for syntax highlighting, formatting, and autocomplete in your schema.
+
+You will create a managed [Prisma Postgres](https://www.prisma.io/postgres) database from the terminal during setup, so you don't need a local database server. If you prefer to run PostgreSQL locally, a Docker alternative is included in the database step.
+
+## Create the project
+
+Create a new directory and initialize a TypeScript project:
+
+```sh
+mkdir grading-app
+cd grading-app
+npm init -y
+npm install typescript tsx @types/node --save-dev
+```
+
+Install Prisma ORM and the packages it needs to talk to PostgreSQL:
+
+```sh
+npm install prisma @types/pg --save-dev
+npm install @prisma/client @prisma/adapter-pg pg dotenv
+```
+
+Here is what each package does:
+
+- `prisma`: the Prisma CLI for running commands like `prisma init`, `prisma migrate`, and `prisma generate`
+- `@prisma/client`: the Prisma Client library for querying your database
+- `@prisma/adapter-pg`: the driver adapter that connects Prisma Client to PostgreSQL through `node-postgres`
+- `pg`: the node-postgres database driver
+- `@types/pg`: TypeScript type definitions for node-postgres
+- `dotenv`: loads environment variables from your `.env` file
+
+Prisma ORM 7 is ESM-first. Update your `tsconfig.json` for ESM compatibility:
+
+```json
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "target": "ES2023",
+ "strict": true,
+ "esModuleInterop": true
+ }
+}
+```
+
+And enable ESM in your `package.json`:
+
+```json
+{
+ "type": "module"
+}
+```
+
+## Create your database
+
+This tutorial uses [Prisma Postgres](https://www.prisma.io/postgres), a managed PostgreSQL database that you can provision straight from the terminal. Create one with:
+
+```sh
+npx create-db
+```
+
+The command provisions a new database and prints a `postgres://...` connection string. Keep that string handy. You will add it to your `.env` file when you initialize Prisma ORM, and you can claim the database in the [Prisma Console](https://console.prisma.io) to keep it.
+
+Prisma Postgres speaks the standard PostgreSQL protocol, so everything in this tutorial works identically against any other PostgreSQL database.
-If you're using Visual Studio Code, the [Prisma extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) is recommended for syntax highlighting, formatting, and other helpers.
+### Alternative: run PostgreSQL locally with Docker
-> **Note**: If you don't want to use Docker, you can set up a [local PostgreSQL database](https://www.prisma.io/dataguide/postgresql/setting-up-a-local-postgresql-database) or a [hosted PostgreSQL database on Heroku](https://dev.to/prisma/how-to-setup-a-free-postgresql-database-on-heroku-1dc1).
+If you would rather run the database locally, create a `docker-compose.yml` file in the project root:
-## Clone the repository
+```yaml
+services:
+ postgres:
+ image: postgres:17
+ restart: always
+ environment:
+ POSTGRES_USER: prisma
+ POSTGRES_PASSWORD: prisma
+ POSTGRES_DB: grading-app
+ ports:
+ - "5432:5432"
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
-The source code for the series can be found on [GitHub](https://github.com/2color/real-world-grading-app).
+volumes:
+ postgres_data:
+```
+
+Start the database:
-To get started, clone the repository and install the dependencies:
+```sh
+docker compose up -d
```
-git clone -b part-1 git@github.com:2color/real-world-grading-app.git
-cd real-world-grading-app
-npm install
+
+## Initialize Prisma ORM
+
+Set up your Prisma ORM project with the following command:
+
+```sh
+npx prisma init --output ../generated/prisma
```
-> **Note:** By checking out the `part-1` branch you'll be able to follow the article from the same starting point.
-## Start PostgreSQL
+This command does three things:
-To start PostgreSQL, run the following command from the `real-world-grading-app` folder:
+- Creates a `prisma/` directory with a `schema.prisma` file for your data model
+- Creates a `.env` file for environment variables
+- Creates a `prisma.config.ts` file for project configuration
+
+The `prisma.config.ts` file is the single place where you configure how Prisma interacts with your project: schema location, migrations, seed scripts, and the database connection. Because it is a TypeScript file, you can load values dynamically, for example with `dotenv`. The generated file looks like this:
+
+```ts
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+export default defineConfig({
+ schema: "prisma/schema.prisma",
+ migrations: {
+ path: "prisma/migrations",
+ },
+ datasource: {
+ url: process.env["DATABASE_URL"],
+ },
+});
+```
+
+The generated `prisma/schema.prisma` uses the `prisma-client` generator. This generator produces a rust-free, ESM-compatible client and writes the generated code into your project source instead of `node_modules`, so your build tools and file watchers treat it like any other part of your app:
+
+```prisma
+generator client {
+ provider = "prisma-client"
+ output = "../generated/prisma"
+}
+
+datasource db {
+ provider = "postgresql"
+}
+```
+
+Finally, set the connection string in `.env`. Use the `postgres://...` URL that `npx create-db` printed when you created your database:
```sh
-docker-compose up -d
+DATABASE_URL="postgres://"
```
-> **Note:** Docker will use the [`docker-compose.yml`](https://github.com/2color/real-world-grading-app/blob/21de326008776144ced60427a055c9fc54a32840/docker-compose.yml) file to start the PostgreSQL container.
+
+If you chose the Docker alternative, use the local connection string instead:
+
+```sh
+DATABASE_URL="postgresql://prisma:prisma@localhost:5432/grading-app"
+```
+
+> **Note:** It's considered best practice to keep secrets out of your codebase. The connection URL is loaded from the environment by `prisma.config.ts`, so the schema file contains no credentials.
## Data model for a grading system for online courses
### Defining the problem domain and entities
-When building a backend, one of the foremost concerns is a proper understanding of the _problem domain_. The problem domain (or problem space) is a term referring to all information that defines the problem and constrains the solution (the constraints being part of the problem).
-By understanding the problem domain, the shape and structure of the data model should become clear.
+When building a backend, one of the foremost concerns is a proper understanding of the _problem domain_. The problem domain (or problem space) refers to all the information that defines the problem and constrains the solution. By understanding the problem domain, the shape and structure of the data model becomes clear.
-The online grading system will have the following entities:
+The online grading system has the following entities:
- **User:** A person with an account. A user can be either a teacher or a student through their relation to a course. In other words, the same user who's a teacher of one course can be a student in another course.
-- **Course:** A learning course with one or more teachers and students as well as one or more tests. For example: an "Introduction to TypeScript" course can have two teachers and ten students.
+- **Course:** A learning course with one or more teachers and students, as well as one or more tests. For example: an "Introduction to TypeScript" course can have two teachers and ten students.
- **Test:** A course can have many tests to evaluate the students' comprehension. Tests have a date and are related to a course.
-- **Test result:** Each test can have multiple test result records per student. Additionally, a TestResult is also related to the teacher who graded the test.
+- **Test result:** Each test can have multiple test result records per student. Additionally, a `TestResult` is also related to the teacher who graded the test.
-> **Note**: An entity represents either a physical object or an intangible concept. For example, a **user** represents a person, whereas a **course** is an intangible concept.
+> **Note:** An entity represents either a physical object or an intangible concept. For example, a **user** represents a person, whereas a **course** is an intangible concept.
The entities can be visualized to demonstrate how they would be represented in a relational database (in this case PostgreSQL). The [diagram](https://dbdiagram.io/d/5f19635fe586385b4ff7a26d) below adds the columns relevant for each entity and foreign keys to describe the relationships between the entities.
@@ -118,244 +239,202 @@ The diagram has the following relations:
- `Test` ↔ `TestResult`
- `Course` ↔ `Test`
- `User` ↔ `TestResult` (via `graderId`)
- - `User` ↔`TestResult` (via `student`)
+ - `User` ↔ `TestResult` (via `studentId`)
- **many-to-many (also known as `m-n`):**
- - `User` ↔ `Course` (via the `CourseEnrollment` [relation table](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#relation-tables) with two _foreign keys_: `userId` and `courseId`). Many-to-many relations typically require an additional table. This is necessary so that the grading system can have the following properties:
+ - `User` ↔ `Course` via the `CourseEnrollment` [relation table](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/many-to-many-relations) with two _foreign keys_: `userId` and `courseId`. Many-to-many relations typically require an additional table. This is necessary so that the grading system can have the following properties:
- A single course can have many associated users (as students or teachers)
- A single user can be associated with many courses.
-> **Note**: A relation table (also known as a JOIN table) connects two or more other tables to create a relation between them. Creating relation tables is a common data modeling practice in SQL to represent relationships between different entities. In essence, it means that "one m-n relation is modeled as two 1-n relations in the database".
+> **Note:** A relation table (also known as a JOIN table) connects two or more other tables to create a relation between them. Creating relation tables is a common data modeling practice in SQL to represent relationships between different entities. In essence, it means that "one m-n relation is modeled as two 1-n relations in the database".
### Understanding the Prisma schema
-To create the tables in your database, you first need to define your [Prisma schema](https://www.prisma.io/docs/concepts/components/prisma-schema). The Prisma schema is a declarative configuration for your database tables which will be used by [Prisma Migrate](https://www.prisma.io/docs/concepts/components/prisma-migrate) to create the tables in your database. Similar to the entity diagram above, it defines the columns and relations between the database tables.
-
-The Prisma schema is used as the source of truth for the generated Prisma Client and Prisma Migrate to create the database schema.
-
-The Prisma schema for the project can be found in [`prisma/schema.prisma`](https://github.com/2color/real-world-grading-app/blob/part-1/prisma/schema.prisma). In the schema you will find stub models which you will define in this step and a `datasource` block. The `datasource` block defines the kind of database that you'll connect to and the connection string. With `env("DATABASE_URL")`, Prisma will load the database connection URL from an environment variable.
-
-> **Note:** It's considered best practice to keep secrets out of your codebase. For this reason the `env("DATABASE_URL")` is defined in the _datasource_ block. By setting an environment variable you keep secrets out of the codebase.
+To create the tables in your database, you first define your [Prisma schema](https://www.prisma.io/docs/orm/prisma-schema). The Prisma schema is a declarative definition of your database tables. It serves as the source of truth for both the generated Prisma Client and for [Prisma Migrate](https://www.prisma.io/docs/orm/prisma-migrate), which creates the database schema.
### Define models
-The fundamental building block of the Prisma schema is [`model`](https://www.prisma.io/docs/concepts/components/prisma-schema/data-model). Every model maps to a database table.
+The fundamental building block of the Prisma schema is the [`model`](https://www.prisma.io/docs/orm/prisma-schema/data-model/models). Every model maps to a database table.
Here is an example showing the basic signature of a model:
```prisma
model User {
- id Int @default(autoincrement()) @id
- email String @unique
+ id Int @id @default(autoincrement())
+ email String @unique
firstName String
lastName String
social Json?
}
```
-Here you define a `User` model with several [fields](https://www.prisma.io/docs/concepts/components/prisma-schema/data-model#fields). Each field has a name followed by a type and optional field attributes. For example, the `id` field could be broken down as follows:
-| Name | Type | Scalar vs Relation | Type modifier | Attributes |
-| :---------- | :------- | :----------------- | :-------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- |
-| `id` | `Int` | Scalar | - | `@id` (denote the primary key) and `@default(autoincrement())` (set a default auto-increment value) |
-| `email` | `String` | Scalar | - | `@unique` |
-| `firstName` | `String` | Scalar | - | - |
-| `lastName` | `String` | Scalar | - | - |
-| `social` | `Json` | Scalar | `?` ([optional](https://www.prisma.io/docs/concepts/components/prisma-schema/data-model#optional-vs-required)) | - |
+Each field has a name followed by a type and optional field attributes. The `User` model breaks down as follows:
+
+| Name | Type | Scalar vs Relation | Type modifier | Attributes |
+| :---------- | :------- | :----------------- | :------------- | :------------------------------------------------ |
+| `id` | `Int` | Scalar | - | `@id` (primary key), `@default(autoincrement())` |
+| `email` | `String` | Scalar | - | `@unique` |
+| `firstName` | `String` | Scalar | - | - |
+| `lastName` | `String` | Scalar | - | - |
+| `social` | `Json` | Scalar | `?` (optional) | - |
-Prisma defines a [set of data types](https://www.prisma.io/docs/concepts/components/prisma-schema/data-model#scalar-types) that map to the native database types depending on the database used.
+Prisma defines a [set of data types](https://www.prisma.io/docs/orm/prisma-schema/data-model/models#scalar-fields) that map to native database types depending on the database used.
-The `Json` data type allows storing free form JSON. This is useful for information that can be inconsistent across `User` records and can change without affecting the core functionality of the backend. In the `User` model above it'd be used to store social links, e.g. Twitter, LinkedIn, etc. Adding new social profile links to the `social` requires no database migration.
+The `Json` type stores free-form JSON. This is useful for information that can vary across `User` records and change without affecting the core functionality of the backend. In the `User` model it stores social links, for example a Bluesky or LinkedIn handle. Adding a new social profile link requires no database migration.
-With a good understanding of your problem domain and modeling data with Prisma, you can now add the following models to your `prisma/schema.prisma` file:
+With a good understanding of the problem domain, add the following models to your `prisma/schema.prisma` file:
```prisma
model User {
- id Int @default(autoincrement()) @id
- email String @unique
+ id Int @id @default(autoincrement())
+ email String @unique
firstName String
lastName String
social Json?
}
model Course {
- id Int @default(autoincrement()) @id
+ id Int @id @default(autoincrement())
name String
courseDetails String?
}
model Test {
- id Int @default(autoincrement()) @id
+ id Int @id @default(autoincrement())
updatedAt DateTime @updatedAt
name String // Name of the test
date DateTime // Date of the test
}
model TestResult {
- id Int @default(autoincrement()) @id
+ id Int @id @default(autoincrement())
createdAt DateTime @default(now())
result Int // Percentage precise to one decimal point represented as `result * 10^-1`
}
```
-Each model has all the relevant fields while ignoring relations (which will be defined in the next step).
+
+Each model has all the relevant fields while ignoring relations, which come next.
### Define relations
#### One-to-many
-In this step you will define a [_one-to-many_](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#one-to-many-relations) relation between `Test` and `TestResult`.
+To define a [one-to-many](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/one-to-many-relations) relation between `Test` and `TestResult`, add the following three fields:
-First, consider the `Test` and `TestResult` models defined in the previous step:
+- A `testId` field of type `Int` (_relation scalar_) on the "many" side of the relation, `TestResult`. This field represents the _foreign key_ in the underlying database table.
+- A `test` field of type `Test` (_relation field_) with a `@relation` attribute mapping the relation scalar `testId` to the `id` primary key of the `Test` model.
+- A `testResults` field of type `TestResult[]` (_relation field_) on `Test`.
```prisma
model Test {
- id Int @default(autoincrement()) @id
- updatedAt DateTime @updatedAt
- name String
- date DateTime
-}
-
-model TestResult {
- id Int @default(autoincrement()) @id
- createdAt DateTime @default(now())
- result Int // Percentage precise to one decimal point represented result * 10^-1
-}
-```
-To define a one-to-many relation between the two models, add the following three fields:
-
-- `testId` field of type `Int` ([_relation scalar_](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#annotated-relation-fields-and-relation-scalar-fields)) on the "many" side of the relation: `TestResult`. This field represents the _foreign key_ in the underlying database table.
-- `test` field of type `Test` ([_relation field_](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#relation-fields)) with a `@relation` attribute mapping the relation scalar `testId` to the `id` primary key of the `Test` model.
-- `testResults` field of type `TestResult[]` ([_relation field_](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#relation-fields))
-
-```prisma
-diff
-model Test {
- id Int @default(autoincrement()) @id
+ id Int @id @default(autoincrement())
updatedAt DateTime @updatedAt
name String
date DateTime
-+ testResults TestResult[] // relation field
+ testResults TestResult[] // relation field
}
model TestResult {
- id Int @default(autoincrement()) @id
+ id Int @id @default(autoincrement())
createdAt DateTime @default(now())
- result Int // Percentage precise to one decimal point represented result * 10^-1
-+ testId Int // relation scalar field
-+ test Test @relation(fields: [testId], references: [id]) // relation field
+ result Int
+
+ testId Int // relation scalar field
+ test Test @relation(fields: [testId], references: [id]) // relation field
}
```
-Relation fields like `test` and `testResults` can be identified by their value type pointing to another model, e.g. `Test` and `TestResult`. Their name will affect the way that relations are accessed programmatically with Prisma Client, however, they don't represent a real database column.
-### Many-to-many relations
+Relation fields like `test` and `testResults` can be identified by their value type pointing to another model. Their names affect how relations are accessed programmatically with Prisma Client, but they don't represent real database columns.
-In this step, you will define a _many-to-many_ relation between the `User` and `Course` models.
+#### Many-to-many
-Many-to-many relations can be [_implicit_ or _explicit_](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#implicit-vs-explicit-many-to-many-relations) in the Prisma schema. In this part, you will learn the difference between the two and when to choose implicit or explicit.
+Many-to-many relations can be [_implicit_ or _explicit_](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/many-to-many-relations) in the Prisma schema.
-First, consider the `User` and `Course` models defined in the previous step:
+To create an implicit many-to-many relation between `User` and `Course`, you would define relation fields as lists on both sides:
```prisma
model User {
- id Int @default(autoincrement()) @id
- email String @unique
+ id Int @id @default(autoincrement())
+ email String @unique
firstName String
lastName String
social Json?
+ courses Course[]
}
model Course {
- id Int @default(autoincrement()) @id
+ id Int @id @default(autoincrement())
name String
courseDetails String?
+ members User[]
}
```
-To create an implicit many-to-many relation, define relation fields as lists on both sides of the relations:
+
+With this, Prisma manages the relation table for you. However, one of the requirements of the grading system is to relate users to a course with a **role**, as either a _teacher_ or a _student_. That means storing meta-information about the relation in the database.
+
+This is what **explicit** many-to-many relations are for. With an explicit relation, you define the relation table as its own model and can add extra fields to it. Define a `CourseEnrollment` model and point the `courses` field on `User` and the `members` field on `Course` at it:
```prisma
-diff
model User {
- id Int @default(autoincrement()) @id
- email String @unique
+ id Int @id @default(autoincrement())
+ email String @unique
firstName String
lastName String
social Json?
-+ courses Course[]
+ courses CourseEnrollment[]
}
model Course {
- id Int @default(autoincrement()) @id
+ id Int @id @default(autoincrement())
name String
courseDetails String?
-+ members User[]
+ members CourseEnrollment[]
}
-```
-With this, Prisma will create the relation table so the grading system can maintain the properties defined above:
-- A single course can have many associated users.
-- A single user can be associated with many courses.
-
-However, one of the requirements of the grading system is to allow relating users to a course with a role as either a _teacher_ or a _student_. This means we need a way to store "meta-information" about the relation in the database.
-
-This can be achieved using an explicit many-to-many relation. The relation table connecting `User` and `Course` requires an extra field to indicate whether the user is a teacher or a student of a course. With explicit many-to-many relations, you can define extra fields on the relation table.
+model CourseEnrollment {
+ createdAt DateTime @default(now())
+ role UserRole
-To do so, define a new model for the relation table named `CourseEnrollment` and update the `courses` field in the `User` model and the `members` field in the `Course` model to type `CourseEnrollment[]` as follows:
+ // Relation fields
+ userId Int
+ user User @relation(fields: [userId], references: [id])
+ courseId Int
+ course Course @relation(fields: [courseId], references: [id])
-```prisma
-diff
-model User {
- id Int @default(autoincrement()) @id
- email String @unique
- firstName String
- lastName String
- social Json?
-+ courses CourseEnrollment[]
+ @@id([userId, courseId])
+ @@index([userId, role])
}
-model Course {
- id Int @default(autoincrement()) @id
- name String
- courseDetails String?
-+ members CourseEnrollment[]
+enum UserRole {
+ STUDENT
+ TEACHER
}
-
-+model CourseEnrollment {
-+ createdAt DateTime @default(now())
-+ role UserRole
-
-+ // Relation Fields
-+ userId Int
-+ user User @relation(fields: [userId], references: [id])
-+ courseId Int
-+ course Course @relation(fields: [courseId], references: [id])
-+ @@id([userId, courseId])
-+ @@index([userId, role])
-+}
-
-+enum UserRole {
-+ STUDENT
-+ TEACHER
-+}
```
+
Things to note about the `CourseEnrollment` model:
- It uses the `UserRole` enum to denote whether a user is a student or a teacher of a course.
-- The `@@id[userId, courseId]` defines a multi-field primary key of the two fields. This will ensure that every `User` can only be associated to a `Course` once, either as a student or as a teacher but not both.
+- `@@id([userId, courseId])` defines a multi-field primary key of the two fields. This ensures that every `User` can only be associated with a `Course` once, either as a student or as a teacher but never both.
-To learn more about relations, check out the [relation docs](https://www.prisma.io/docs/concepts/components/prisma-schema/relations).
+To learn more about relations, check out the [relations documentation](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations).
### The full schema
-Now that you've seen how relations are defined, update the [Prisma schema](https://github.com/2color/real-world-grading-app/blob/part-1/prisma/schema.prisma) with the following:
+Now that you've seen how relations are defined, update `prisma/schema.prisma` with the complete data model:
```prisma
+generator client {
+ provider = "prisma-client"
+ output = "../generated/prisma"
+}
+
datasource db {
provider = "postgresql"
- url = env("DATABASE_URL")
}
model User {
- id Int @id @default(autoincrement())
- email String @unique
+ id Int @id @default(autoincrement())
+ email String @unique
firstName String
lastName String
social Json?
@@ -380,7 +459,7 @@ model CourseEnrollment {
createdAt DateTime @default(now())
role UserRole
- // Relation Fields
+ // Relation fields
userId Int
courseId Int
user User @relation(fields: [userId], references: [id])
@@ -396,7 +475,7 @@ model Test {
name String
date DateTime
- // Relation Fields
+ // Relation fields
courseId Int
course Course @relation(fields: [courseId], references: [id])
testResults TestResult[]
@@ -407,7 +486,7 @@ model TestResult {
createdAt DateTime @default(now())
result Int // Percentage precise to one decimal point represented as `result * 10^-1`
- // Relation Fields
+ // Relation fields
studentId Int
student User @relation(name: "results", fields: [studentId], references: [id])
graderId Int
@@ -421,77 +500,102 @@ enum UserRole {
TEACHER
}
```
-Note that `TestResult` has two relations to the `User` model: `student` and `gradedBy` to represent both the teacher who graded the test and the student who took the test. The `name` argument on the `@relation` attribute is necessary to [disambiguate the relation](https://www.prisma.io/docs/concepts/components/prisma-schema/relations#disambiguating-relations) when a single model has more than one relation to the same model.
-## Migrating the database
+Note that `TestResult` has two relations to the `User` model: `student` and `gradedBy`, representing both the student who took the test and the teacher who graded it. The `name` argument on the `@relation` attribute [disambiguates the relations](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations#disambiguating-relations) when a single model has more than one relation to the same model.
-With the Prisma schema defined, you will now use Prisma Migrate to create the actual tables in the database.
+## Migrating the database
-First, set the `DATABASE_URL` environment variable locally so that Prisma can connect to your database.
+With the Prisma schema defined, use Prisma Migrate to create the actual tables in the database:
```sh
-export DATABASE_URL="postgresql://prisma:prisma@127.0.0.1:5432/grading-app"
+npx prisma migrate dev --name init
```
-> **Note**: The username and password for the local database are both defined as `prisma` in [`docker-compose.yml`](https://github.com/2color/real-world-grading-app/blob/21de326008776144ced60427a055c9fc54a32840/docker-compose.yml#L12-L13).
-
-To create and run a migration with Prisma Migrate, run the following command in your terminal:
-```npm
-npx prisma migrate dev --preview-feature --skip-generate --name "init"
-```
-The command will do two things:
-- **Save the migration:** Prisma Migrate will take a snapshot of your schema and figure out the SQL necessary to carry out the migration. The migration file containing the SQL will be saved to `prisma/migrations`
-- **Run the migration:** Prisma Migrate will execute the SQL in the migration file to run the migration and alter (or create) the database schema
+The command does two things:
-> **Note:** Prisma Migrate is currently in [preview](https://www.prisma.io/docs/about/prisma/releases#preview) mode. This means that it is not recommended to use Prisma Migrate in production.
+- **Saves the migration:** Prisma Migrate takes a snapshot of your schema and figures out the SQL necessary to carry out the migration. The migration file containing the SQL is saved to `prisma/migrations`.
+- **Runs the migration:** Prisma Migrate executes the SQL in the migration file to create the database schema.
+**Checkpoint:** You should see output similar to the following:
-**Checkpoint:** You should see something like the following in the output:
```
-Prisma Migrate created and applied the following migration(s) from new schema changes:
-
migrations/
- └─ 20201202091734_init/
+ └─ 20260703110127_init/
└─ migration.sql
-Everything is now in sync.
+Your database is now in sync with your schema.
```
-Congratulations, you have successfully designed the data model and created the database schema. In the next step, you will use Prisma Client to perform CRUD and aggregation queries against your database.
+
+Congratulations, you have successfully designed the data model and created the database schema. In the next step, you will use Prisma Client to query the database.
## Generating Prisma Client
-Prisma Client is an auto-generated database client that's tailored to your database schema. It works by parsing the Prisma schema and generating a TypeScript client that you can import in your code.
+Prisma Client is a type-safe database client generated from your Prisma schema. It exposes a TypeScript API tailored to your models, with autocomplete and compile-time guarantees for every query.
-Generating Prisma Client, typically requires three steps:
+Generate it with:
-1. Add the following `generator` definition to your Prisma schema:
- ```prisma
- generator client {
- provider = "prisma-client-js"
- }
- ```
-1. Install the `@prisma/client` npm package
-```
- npm install --save @prisma/client
- ```
-1. Generate Prisma Client with the following command:
+```sh
+npx prisma generate
```
- npx prisma generate
- ```
-**Checkpoint:** You should see the following in the output: `✔ Generated Prisma Client to ./node_modules/@prisma/client in 57ms`
+**Checkpoint:** You should see output similar to: `✔ Generated Prisma Client (7.8.0) to ./generated/prisma`
+
+The client is generated into `generated/prisma` in your project, as configured by the `output` option of the generator. Prisma ORM 7 generates the client into your project source rather than `node_modules`. Your dev server, bundler, and file watchers see the generated code as regular application code, and regenerating after a schema change no longer requires restarting tooling that caches `node_modules`.
## Seeding the database
-In this step, you will use Prisma Client to write a seed script to fill the database with some sample data.
+In this step, you will write a seed script that fills the database with sample data using Prisma Client CRUD operations. You will also use [nested writes](https://www.prisma.io/docs/orm/prisma-client/queries/relation-queries#nested-writes) to create database rows for related entities in a single operation.
+
+First, register the seed script in `prisma.config.ts` so the Prisma CLI knows how to run it:
+
+```ts
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+export default defineConfig({
+ schema: "prisma/schema.prisma",
+ migrations: {
+ path: "prisma/migrations",
+ seed: "tsx prisma/seed.ts",
+ },
+ datasource: {
+ url: process.env["DATABASE_URL"],
+ },
+});
+```
+
+Then create `prisma/seed.ts` with the client setup. In Prisma ORM 7 you instantiate Prisma Client with a [driver adapter](https://www.prisma.io/docs/orm/overview/databases/database-drivers), which manages the connection to PostgreSQL through `node-postgres`:
+
+```ts
+import "dotenv/config";
+import { PrismaPg } from "@prisma/adapter-pg";
+import { PrismaClient } from "../generated/prisma/client";
+
+const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
+const prisma = new PrismaClient({ adapter });
+
+function addDays(date: Date, days: number): Date {
+ return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
+}
-A _seed script_ in this context is a bunch of CRUD operations (create, read, update, and delete) with Prisma Client. You will also use [nested writes](https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#nested-writes) to create database rows for related entities in a single operation.
+async function main() {
+ // Seed operations go here, step by step below
+}
-Open the skeleton `src/seed.ts` file, where you will find the Prisma Client imported and two Prisma Client function calls: one to instantiate Prisma Client and the other to disconnect when the script finishes running.
+main()
+ .then(async () => {
+ await prisma.$disconnect();
+ })
+ .catch(async (e) => {
+ console.error(e);
+ await prisma.$disconnect();
+ process.exit(1);
+ });
+```
### Creating a user
-Begin by creating a user as follows in `main` function:
+Begin by creating a user inside the `main` function:
```ts
const grace = await prisma.user.create({
@@ -500,28 +604,22 @@ const grace = await prisma.user.create({
firstName: 'Grace',
lastName: 'Bell',
social: {
- facebook: 'gracebell',
- twitter: 'therealgracebell',
+ bluesky: 'gracebell',
+ linkedin: 'gracebell',
},
},
})
```
-The operation will create a row in the _User_ table and return the created user (including the created `id`). It's worth noting that `user` will infer the type `User` which is defined in `@prisma/client`:
-```ts
-export type User = {
- id: number
- email: string
- firstName: string
- lastName: string
- social: JsonValue | null
-}
-```
-To execute the seed script and create the `User` record, you can use the [`seed` script in the `package.json`](https://github.com/2color/real-world-grading-app/blob/part-1/package.json#L25) as follows:
-```
-npm run seed
+The operation creates a row in the `User` table and returns the created user, including the generated `id`. The returned `grace` value is fully typed: the `User` type is exported from your generated client, so you can also import it directly with `import type { User } from "../generated/prisma/client"`.
+
+Run the seed script with:
+
+```sh
+npx prisma db seed
```
-As you follow the next steps, you will run the seed script more than once. To avoid hitting unique constraint errors, you can delete the contents of the database in the beginning of the `main` functions as follows:
+
+As you follow the next steps, you will run the seed script more than once. To avoid hitting unique constraint errors, delete the contents of the database at the beginning of the `main` function:
```ts
await prisma.testResult.deleteMany({})
@@ -530,71 +628,26 @@ await prisma.test.deleteMany({})
await prisma.user.deleteMany({})
await prisma.course.deleteMany({})
```
+
> **Note:** These commands delete all rows in each database table. Use carefully and avoid this in production!
### Creating a course and related tests and users
-In this step, you will create a _course_ and use a nested write to create related _tests_.
-
-Add the following to the `main` function:
+In this step, you will create a _course_ and use a **nested write** to create related _tests_ in the same operation. Add the following to the `main` function:
```ts
-const weekFromNow = add(new Date(), { days: 7 })
-const twoWeekFromNow = add(new Date(), { days: 14 })
-const monthFromNow = add(new Date(), { days: 28 })
+const weekFromNow = addDays(new Date(), 7)
+const twoWeeksFromNow = addDays(new Date(), 14)
+const monthFromNow = addDays(new Date(), 28)
const course = await prisma.course.create({
data: {
name: 'CRUD with Prisma',
tests: {
create: [
- {
- date: weekFromNow,
- name: 'First test',
- },
- {
- date: twoWeekFromNow,
- name: 'Second test',
- },
- {
- date: monthFromNow,
- name: 'Final exam',
- },
- ],
- },
- },
-})
-```
-This will create a row in the `Course` table and three related rows in the `Tests` table (`Course` and `Tests` have a one-to-many relationship which allows this).
-
-What if you wanted to create a relation between the user created in the previous step to this course as a teacher?
-
-`User` and `Course` have an explicit many-to-many relationship. That means that we have to create rows in the `CourseEnrollment` table and assign a role to link a `User` to a `Course`.
-
-This can be done as follows (adding to the query from the previous step):
-
-```ts
-const weekFromNow = add(new Date(), { days: 7 })
-const twoWeekFromNow = add(new Date(), { days: 14 })
-const monthFromNow = add(new Date(), { days: 28 })
-
-const course = await prisma.course.create({
- data: {
- name: 'CRUD with Prisma',
- tests: {
- create: [
- {
- date: weekFromNow,
- name: 'First test',
- },
- {
- date: twoWeekFromNow,
- name: 'Second test',
- },
- {
- date: monthFromNow,
- name: 'Final exam',
- },
+ { date: weekFromNow, name: 'First test' },
+ { date: twoWeeksFromNow, name: 'Second test' },
+ { date: monthFromNow, name: 'Final exam' },
],
},
members: {
@@ -613,22 +666,23 @@ const course = await prisma.course.create({
},
})
```
-> **Note:** the [`include`](https://www.prisma.io/docs/concepts/components/prisma-client/select-fields#include) argument allows you to fetch relations in the result. This will be useful in a later step to relate test results with tests
-When using nested writes (as with `members` and `tests`) there are two options:
+This creates one row in the `Course` table and three related rows in the `Test` table through their one-to-many relation. It also relates Grace to the course as a teacher through the explicit many-to-many relation: a row is created in the `CourseEnrollment` relation table with the `TEACHER` role.
+
+When using nested writes, there are two options:
-- **`connect`**: Create a relation with an existing row
-- **`create`**: Create a new row and relation
+- **`connect`**: Create a relation to an existing row
+- **`create`**: Create a new row and the relation to it
-In the case of `tests`, you passed an array of objects which are linked to the created course.
+In the case of `tests`, you passed an array of objects to `create` which are all linked to the created course.
-In the case of `members`, both `create` and `connect` were used. This is necessary because even though the `user` already exists, a _new_ row in the relation table (`CourseEnrollment` referenced by `members`) needs to be created which uses `connect` to form a relation with the previously-created user.
+In the case of `members`, both `create` and `connect` were used: even though the user already exists, a _new_ row in the `CourseEnrollment` relation table needs to be created, and `connect` links it to the existing user.
-### Creating users and relating to a course
+> **Note:** The [`include`](https://www.prisma.io/docs/orm/prisma-client/queries/select-fields) argument fetches relations in the result. Here it returns the created tests, which you will need to relate test results to tests in a later step.
-In the previous step, you created a course, related tests, and assigned a teacher to the course. In this step you will create more users and relate them to the course as _students_.
+### Creating users and relating them to a course
-Add the following statements:
+Next, create more users and relate them to the course as _students_:
```ts
const shakuntala = await prisma.user.create({
@@ -663,28 +717,10 @@ const david = await prisma.user.create({
},
})
```
-### Adding test results for the students
-Looking at the `TestResult` model, it has three relations: `student`, `gradedBy`, and `test`. To add test results for Shakuntala and David, you will use nested writes similarly to the previous steps.
+### Adding test results for the students
-Here is the `TestResult` model again for reference:
-
-```prisma
-model TestResult {
- id Int @default(autoincrement()) @id
- createdAt DateTime @default(now())
- result Int // Percentage precise to one decimal point represented as `result * 10^-1`
-
- // Relation Fields
- studentId Int
- student User @relation(name: "results", fields: [studentId], references: [id])
- graderId Int
- gradedBy User @relation(name: "graded", fields: [graderId], references: [id])
- testId Int
- test Test @relation(fields: [testId], references: [id])
-}
-```
-Adding a single test result would look as follows:
+Looking at the `TestResult` model, it has three relations: `student`, `gradedBy`, and `test`. Adding a single test result looks as follows:
```ts
await prisma.testResult.create({
@@ -702,7 +738,8 @@ await prisma.testResult.create({
},
})
```
-To add a test result for both David and Shakuntala for each of the three tests, you can create a loop:
+
+To add a test result for both David and Shakuntala for each of the three tests, loop over the course's tests:
```ts
const testResultsDavid = [650, 900, 950]
@@ -712,30 +749,18 @@ let counter = 0
for (const test of course.tests) {
await prisma.testResult.create({
data: {
- gradedBy: {
- connect: { email: grace.email },
- },
- student: {
- connect: { email: shakuntala.email },
- },
- test: {
- connect: { id: test.id },
- },
+ gradedBy: { connect: { email: grace.email } },
+ student: { connect: { email: shakuntala.email } },
+ test: { connect: { id: test.id } },
result: testResultsShakuntala[counter],
},
})
await prisma.testResult.create({
data: {
- gradedBy: {
- connect: { email: grace.email },
- },
- student: {
- connect: { email: david.email },
- },
- test: {
- connect: { id: test.id },
- },
+ gradedBy: { connect: { email: grace.email } },
+ student: { connect: { email: david.email } },
+ test: { connect: { id: test.id } },
result: testResultsDavid[counter],
},
})
@@ -743,19 +768,22 @@ for (const test of course.tests) {
counter++
}
```
-Congratulations, if you have reached this point, you successfully created sample data for users, courses, tests, and test results in your database.
-To explore the data in the database, you can run [Prisma Studio](https://www.prisma.io/docs/concepts/components/prisma-studio). Prisma Studio is a visual editor for your database. To run Prisma Studio, run the following command in your terminal:
-```
+Congratulations, you have created sample data for users, courses, tests, and test results in your database.
+
+To explore the data visually, run [Prisma Studio](https://www.prisma.io/studio). Prisma Studio is a visual editor for your database:
+
+```sh
npx prisma studio
```
+
## Aggregating the test results with Prisma Client
-Prisma Client allows you to perform aggregate operations on number fields (such as `Int` and `Float`) of a model. Aggregate operations compute a single result from a set of input values, i.e. multiple rows in a table. For example, calculating the _minimum_, _maximum_, and _average_ value of the `result` column over a set of `TestResult` rows.
+Prisma Client can perform aggregate operations on the number fields (such as `Int` and `Float`) of a model. Aggregate operations compute a single result from a set of rows, for example the _minimum_, _maximum_, and _average_ of the `result` column over a set of `TestResult` rows.
In this step, you will run two kinds of aggregate operations:
-1. For each **test** in the course across all **students**, resulting in aggregates representing how difficult the test was or the class' comprehension of the test's topic:
+1. For each **test** in the course across all **students**, resulting in aggregates representing how difficult the test was:
```ts
for (const test of course.tests) {
@@ -763,10 +791,10 @@ In this step, you will run two kinds of aggregate operations:
where: {
testId: test.id,
},
- avg: { result: true },
- max: { result: true },
- min: { result: true },
- count: true,
+ _avg: { result: true },
+ _max: { result: true },
+ _min: { result: true },
+ _count: true,
})
console.log(`test: ${test.name} (id: ${test.id})`, results)
}
@@ -776,22 +804,22 @@ In this step, you will run two kinds of aggregate operations:
```
test: First test (id: 1) {
- avg: { result: 725 },
- max: { result: 800 },
- min: { result: 650 },
- count: 2
+ _avg: { result: 725 },
+ _max: { result: 800 },
+ _min: { result: 650 },
+ _count: 2
}
test: Second test (id: 2) {
- avg: { result: 925 },
- max: { result: 950 },
- min: { result: 900 },
- count: 2
+ _avg: { result: 925 },
+ _max: { result: 950 },
+ _min: { result: 900 },
+ _count: 2
}
test: Final exam (id: 3) {
- avg: { result: 930 },
- max: { result: 950 },
- min: { result: 910 },
- count: 2
+ _avg: { result: 930 },
+ _max: { result: 950 },
+ _min: { result: 910 },
+ _count: 2
}
```
@@ -803,10 +831,10 @@ In this step, you will run two kinds of aggregate operations:
where: {
student: { email: david.email },
},
- avg: { result: true },
- max: { result: true },
- min: { result: true },
- count: true,
+ _avg: { result: true },
+ _max: { result: true },
+ _min: { result: true },
+ _count: true,
})
console.log(`David's results (email: ${david.email})`, davidAggregates)
@@ -815,10 +843,10 @@ In this step, you will run two kinds of aggregate operations:
where: {
student: { email: shakuntala.email },
},
- avg: { result: true },
- max: { result: true },
- min: { result: true },
- count: true,
+ _avg: { result: true },
+ _max: { result: true },
+ _min: { result: true },
+ _count: true,
})
console.log(`Shakuntala's results (email: ${shakuntala.email})`, shakuntalaAggregates)
```
@@ -827,30 +855,44 @@ In this step, you will run two kinds of aggregate operations:
```
David's results (email: david@prisma.io) {
- avg: { result: 833.3333333333334 },
- max: { result: 950 },
- min: { result: 650 },
- count: 3
+ _avg: { result: 833.3333333333334 },
+ _max: { result: 950 },
+ _min: { result: 650 },
+ _count: 3
}
Shakuntala's results (email: devi@prisma.io) {
- avg: { result: 886.6666666666666 },
- max: { result: 950 },
- min: { result: 800 },
- count: 3
+ _avg: { result: 886.6666666666666 },
+ _max: { result: 950 },
+ _min: { result: 800 },
+ _count: 3
}
```
-## Summary and next steps
+### Grouping aggregates with groupBy
-This article covered a lot of ground starting with the problem domain and then delving into data modeling, the Prisma Schema, database migrations with Prisma Migrate, CRUD with Prisma Client, and aggregations.
+Prisma Client also supports [`groupBy`](https://www.prisma.io/docs/orm/prisma-client/queries/aggregation-grouping-summarizing#group-by) queries, which combine grouping and aggregation in a single database query. The per-test loop above can be expressed as one query:
-Mapping out the problem domain is generally good advice before jumping into the code because it informs the design of the data model which impacts every aspect of the backend.
+```ts
+const resultsByTest = await prisma.testResult.groupBy({
+ by: ["testId"],
+ _avg: { result: true },
+ _max: { result: true },
+ _min: { result: true },
+ _count: true,
+})
+```
+
+This returns one row per `testId` with the same aggregate values as the loop, using a single `GROUP BY` query in PostgreSQL instead of one query per test.
+
+## Summary and next steps
+
+This article covered a lot of ground, starting with the problem domain and then delving into data modeling, the Prisma schema, database migrations with Prisma Migrate, CRUD operations with Prisma Client, and aggregations.
-While Prisma aims to make working with relational databases easy it can be helpful to have a deeper understanding of the underlying database.
+Mapping out the problem domain before jumping into code is generally good advice because it informs the design of the data model, which impacts every aspect of the backend. And because the Prisma schema is the single source of truth for both your database structure and your generated client, the data model you designed here carries through every layer of the application, whether the code is written by you, your team, or an AI agent working from the same schema.
-Check out the [Prisma's Data Guide](https://www.prisma.io/dataguide) to learn more about how databases work, how to choose the right one, and how to use databases with your applications to their full potential.
+While Prisma aims to make working with relational databases easy, it helps to have a deeper understanding of the underlying database. Check out [Prisma's Data Guide](https://www.prisma.io/dataguide) to learn more about how databases work.
-In the next parts of the series, you'll learn more about:
+In the next parts of this series, you'll learn more about:
- API layer
- Validation
@@ -860,4 +902,6 @@ In the next parts of the series, you'll learn more about:
- Integration with external APIs
- Deployment
-**Join for the [the next live stream](https://youtu.be/d9v7umfMNkM) which will be streamed live on YouTube at 6:00 PM CEST August 12th.**
+The database you created with `npx create-db` runs on [Prisma Postgres](https://www.prisma.io/postgres), which you can claim and manage in the [Prisma Console](https://console.prisma.io). If AI agents are part of your development workflow, the [Prisma MCP server](https://www.prisma.io/docs/postgres/integrations/mcp-server) lets them create and manage databases as they need them. And when you reach the deployment part of this series, [Prisma Compute](https://www.prisma.io/compute) runs your backend on the same platform as your database.
+
+This series teaches Prisma ORM 7, the current production release. If you want to see where Prisma ORM is heading for AI-assisted development, read [The Next Evolution of Prisma ORM](https://www.prisma.io/blog/the-next-evolution-of-prisma-orm). Prisma Next is the agent-native evolution of the ORM, available in Early Access today, and it becomes Prisma 8 at GA. It builds on the idea at the heart of this article: your schema acts as a single data contract, and every query against it is type-safe and returns structured output that humans and coding agents can build on safely. It runs in parallel with an existing project so you can migrate incrementally, and the [performance benchmark](https://www.prisma.io/blog/prisma-next-performance-benchmark) covers what the rewrite means for throughput and client size.