apps/blog/content/blog/datamodel-v11-lrzqy1f56c90/index.mdx
Prisma's latest release features an improved datamodel syntax. It removes many of the opinionated decisions Prisma used to make about the database layout and enables more control for developers.
⚠️ This article is outdated as it relates to Prisma 1 which is now deprecated. To learn more about the most recent version of Prisma, read the documentation. ⚠️
Over the last months, we have worked with the community to define an improved datamodel specification for Prisma. This new version is called datamodel v1.1 and is available in today's stable release. Check out the docs here.
As of today, Prisma's public Demo servers will be using the new datamodel syntax. Check out the docs or this tutorial video to learn how to upgrade your existing projects.
A datamodel is the foundation for every Prisma project. It serves as the foundation for the schema of the underlying database.
The current datamodel is opinionated about the database layout, e.g. for relations, naming of tables/columns or system fields. The new datamodel syntax lifts many limitations so that developers have more control over their schema.
Here are a few things enabled by the new datamodel syntax:
id field and "bring your own ID"createdAt or updatedAt fieldsIn previous Prisma versions, developers had to decide whether Prisma should perform database migrations for them, by setting the migrations flag in PRISMA_CONFIG.
The migrations flag has been removed in the latest Prisma version, meaning developers can now at all times either migrate the database manually or use Prisma for the migration.
We have also invested a lot into the introspection of existing databases, enabling smooth workflows for developers that are using Prisma with a legacy database or need to perform manual migrations at some point.
With the old datamodel syntax, tables and columns are always named exactly after the models and fields in your datamodel. Using the new @db directive, you can control what tables and columns should be called in the underlying database:
type User @db(name: "user") {
id: ID! @id
name: String! @db(name: "full_name")
}
CREATE TABLE "default$default"."user" (
"id" varchar(25) NOT NULL,
"full_name" text NOT NULL,
PRIMARY KEY ("id")
);
In this case, the underlying table will be called user and the column full_name.
The old datamodel is opinionated about relations in the database schema: they're always represented as relation tables.
On the one hand, this makes it possible to easily migrate any existing relation to a many-to-many-relation without extra work. However, there might be a performance penalty to pay for this flexibility because relation tables are often more expensive to query.
While 1:1 and 1:n relations can now be represented via foreign keys, m:n relations will keep being represented as relation tables.
With the new datamodel, developers can take full control over expressing a relation in the underlying database. There are two options:
Here is an example with two relations (one is inline, the other uses a relation table):
type User {
id: ID! @id
profile: Profile! @relation(link: INLINE)
posts: [Post!]! @relation(link: TABLE)
}
type Profile {
id: ID! @id
user: User!
}
type Post {
id: ID! @id
author: User!
}
CREATE TABLE "default$default"."User" (
"id" varchar(25) NOT NULL,
"profile" varchar(25),
PRIMARY KEY ("id")
);
CREATE TABLE "default$default"."Profile" (
"id" varchar(25) NOT NULL,
PRIMARY KEY ("id")
);
CREATE TABLE "default$default"."Post" (
"id" varchar(25) NOT NULL,
PRIMARY KEY ("id")
);
CREATE TABLE "default$default"."_PostToUser" (
"A" varchar(25) NOT NULL,
"B" varchar(25) NOT NULL
);
In the case of the inline relation, the placement of the @relation(link: INLINE) directive determines on which end of the relation the foreign key is being stored, in this example it's stored in the User table.
id, createdAt or updatedAtWith the old datamodel, developers were required to use reserved fields if they wanted to automatically generate unique IDs or track when a record was created/last updated.
With the new @id, @createdAt and @updatedAt directives, it is now possible to add this functionality to any field of a model:
type User {
myID: ID! @id
myCreatedAt: DateTime! @createdAt
myUpdatedAt: DateTime! @updatedAt
}
CREATE TABLE "test$devasdas"."User" (
"myID" varchar(25) NOT NULL,
"myCreatedAt" timestamp(3) NOT NULL,
"myUpdatedAt" timestamp(3) NOT NULL,
PRIMARY KEY ("myID")
);
The current datamodel always uses CUIDs to generate and store globally unique IDs for database records. The datamodel v1.1 now makes it possible to maintain custom IDs as well as to use other ID types (e.g. integers, sequences, or UUIDs).
We prepared two short tutorials for you to explore the new datamodel:
For more extensive tutorials and instructions for getting started with an existing database, visit the docs.
To install the latest version of the Prisma CLI, run:
npm install -g prisma
When running Prisma with Docker, you need to upgrade its Docker image to
1.31.
When upgrading your existing Prisma projects, you can use simply run prisma introspect to generate the datamodel with the new syntax. The exact process is described with an example in the following sections and this video:
Assume you already have a running Prisma project that uses an (old) datamodel.
<Accordions type="single"> <Accordion title="See sample datamodel"> ```graphql type User { id: ID! @unique createdAt: DateTime! email: String! @unique name: String role: Role @default(value: "USER") posts: [Post!]! profile: Profile }type Profile { id: ID! @unique user: User! bio: String! }
type Post { id: ID! @unique createdAt: DateTime! updatedAt: DateTime! title: String! published: Boolean! @default(value: "false") author: User! categories: [Category!]! }
type Category { id: ID! @unique name: String! posts: [Post!]! }
enum Role { USER ADMIN }
When using the old datamodel, the following tables are created by Prisma in the underlying database:
- `User`
- `Profile`
- `Post`
- `Category`
- `_CategoryToPost`
- `_PostToUser`
- `_ProfileToUser`
- `_RelayId`
Each relation is represented via a relation table. The `_RelayId` table is used to identify any record by its ID. With the old datamodel syntax, these are decisions made by Prisma that can not be worked around.
</Accordion>
</Accordions>
#### 2. Upgrade your Prisma server
In the Docker Compose file used to deploy your Prisma server, make sure to use the latest `1.31` Prisma version for the `prismagraphql/prisma` image. For example:
```yml
version: '3'
services:
prisma:
image: prismagraphql/prisma:1.31
restart: always
ports:
- '4466:4466'
environment:
PRISMA_CONFIG: |
port: 4466
databases:
default:
connector: postgres
host: localhost
user: prisma
password: prisma
port: '5432'
Now upgrade the running Prisma server:
docker-compose up -d
If you're now running prisma deploy, your Prisma CLI will throw an error because you're trying to deploy a datamodel in the old syntax to an updated Prisma server.
The easiest way to fix these errors is by generating a datamodel written in the new syntax via introspection. Run the following command inside the directory where your prisma.yml is located:
prisma introspect
This introspects your database and generates another datamodel with the new syntax, called datamodel-TIMESTAMP.prisma (e.g. datamodel-1554394432089.prisma). For the example from above, the following datamodel is generated:
type User {
id: ID! @id
createdAt: DateTime! @createdAt
updatedAt: DateTime! @updatedAt
name: String
email: String! @unique
role: Role @default(value: USER)
posts: [Post]
profile: Profile @relation(link: TABLE)
}
type Profile {
id: ID! @id
createdAt: DateTime! @createdAt
updatedAt: DateTime! @updatedAt
user: User!
bio: String!
}
type Post {
id: ID! @id
createdAt: DateTime! @createdAt
updatedAt: DateTime! @updatedAt
title: String!
published: Boolean! @default(value: false)
author: User! @relation(link: TABLE)
categories: [Category]
}
type Category {
id: ID! @id
createdAt: DateTime! @createdAt
updatedAt: DateTime! @updatedAt
name: String!
posts: [Post]
}
enum Role {
USER
ADMIN
}
The final step is to delete the old datamodel.prisma file and rename your generated datamodel to datamodel.prisma (so that the datamodel property in your prisma.yml points to the generated file that's using the new syntax).
Once that's done, you can run:
prisma deploy
Because the introspection didn't change anything about your database layout, all relations are still represented as relation tables. If you want to learn how you can migrate the old 1:1 and 1:n relations to use foreign keys, check out the docs here.
After having learned how to upgrade existing Prisma projects, we'll now walk you through a simple setup where we're starting out from scratch.
Let's start by setting up a new Prisma project:
prisma init hello-datamodel
In the interactive wizard, select the following:
Before launching the Prisma server and the database via Docker, enable port mapping for your database. This will later allow you to connect to the database using a local DB client (such as Postico or TablePlus).
In the generated docker-compose.yml, uncomment the following lines in the Docker image configuration of the database:
ports:
- '5432:5432'
ports:
- '3306:3306'
Let's define a datamodel that takes advantage of the new Prisma features. Open datamodel.prisma and replace the contents with the following:
type User @db(name: "user") {
id: ID! @id
createdAt: DateTime! @createdAt
email: String! @unique
name: String
role: Role @default(value: USER)
posts: [Post!]!
profile: Profile @relation(link: INLINE)
}
type Profile @db(name: "profile") {
id: ID! @id
user: User!
bio: String!
}
type Post @db(name: "post") {
id: ID! @id
createdAt: DateTime! @createdAt
updatedAt: DateTime! @updatedAt
author: User!
published: Boolean! @default(value: false)
categories: [Category!]! @relation(link: TABLE, name: "PostToCategory")
}
type Category @db(name: "category") {
id: ID! @id
name: String!
posts: [Post!]! @relation(name: "PostToCategory")
}
type PostToCategory @db(name: "post_to_category") @relationTable {
post: Post
category: Category
}
enum Role {
USER
ADMIN
}
Here are some important bits about this datamodel definition:
@db directive.User and ProfileUser and PostPost and CategoryUser and Profile is annotated with @relation(link: INLINE) on the User model. This means user records in the database have a reference to a profile record if the relation is present (because the profile field is not required, the relation might just be NULL). An alternative to INLINE is TABLE in which case Prisma would track the relation via a dedicated relation table.User and Post is is tracked inline the relation via the author column of the post table, i.e. the @relation(link: INLINE) directive is inferred on the author field of the Post model.Post and Category is tracked via a dedicated relation table called PostToCategory. This relation table is part of the datamodel and annotated with the @relationTable directive.id field annotated with the @id directive.User model, the database automatically tracks when a record is created via the field annotated with the @createdAt directive.Post model, the database automatically tracks when a record is created and updated via the fields annotated with the @createdAt and @updatedAt directives.In the next step, Prisma will map this datamodel to the underlying database:
prisma deploy
Table:
CREATE TABLE "hello-datamodel$dev"."category" (
"id" varchar(25) NOT NULL,
"name" text NOT NULL,
PRIMARY KEY ("id")
);
Index:
| index_name | index_algorithm | is_unique | column_name |
|---|---|---|---|
category_pkey | BTREE | TRUE | id |
PostTable:
CREATE TABLE "hello-datamodel$dev"."post" (
"id" varchar(25) NOT NULL,
"author" varchar(25),
"published" bool NOT NULL,
"createdAt" timestamp(3) NOT NULL,
"updatedAt" timestamp(3) NOT NULL,
"title" text NOT NULL,
PRIMARY KEY ("id")
);
Index:
| index_name | index_algorithm | is_unique | column_name |
|---|---|---|---|
post_pkey | BTREE | TRUE | id |
PostToCategoryTable:
CREATE TABLE "hello-datamodel$dev"."post_to_category" (
"category" varchar(25) NOT NULL,
"post" varchar(25) NOT NULL
);
Index:
| index_name | index_algorithm | is_unique | column_name |
|---|---|---|---|
post_to_category_AB_unique | BTREE | TRUE | category,post |
post_to_category_B | BTREE | FALSE | post |
ProfileTable:
CREATE TABLE "hello-datamodel$dev"."profile" (
"id" varchar(25) NOT NULL,
"bio" text NOT NULL,
PRIMARY KEY ("id")
);
Index:
| index_name | index_algorithm | is_unique | column_name |
|---|---|---|---|
profile_pkey | BTREE | TRUE | id |
UserTable:
CREATE TABLE "hello-datamodel$dev"."user" (
"id" varchar(25) NOT NULL,
"email" text NOT NULL,
"name" text,
"role" text NOT NULL,
"createdAt" timestamp(3) NOT NULL,
"profile" varchar(25),
PRIMARY KEY ("id")
);
Index:
| index_name | index_algorithm | is_unique | column_name |
|---|---|---|---|
user_pkey | BTREE | TRUE | id |
hello-datamodel$dev.user.email._UNIQUE | BTREE | TRUE | email |
From here on, you can use the Prisma client if you want to access the data in your database programmatically. In the following, we'll highlight how to use Prisma Admin to interact with the data.
Visit the docs to learn how you can connect to the database using TablePlus and explore the underlying database schema.
To access your data in Prisma Admin, you need to navigate to the Admin endpoint of your Prisma project: http://localhost:4466/_admin
While the new datamodel syntax already incorporates many features requested by our community, we still see opportunities to improve it even further. For example, the datamodel doesn't yet provide multi-column indices and polymorphic relations.
We are currently working on a new data modeling language that will be a variation of the currently used SDL.
We'd love hear what you think of the new datamodel. Please share your feedback by opening an issue in the feedback repo or join the conversation on Spectrum.