This page compares Prisma and TypeORM. If you want to learn how to migrate from TypeORM to Prisma, check out this guide.

TypeORM vs Prisma

While Prisma and TypeORM solve similar problems, they work in very different ways.

TypeORM is a traditional ORM which maps tables to model classes. These model classes can be used to generate SQL migrations. Instances of the model classes then provide an interface for CRUD queries to an application at runtime.

Prisma is a new kind of ORM that mitigates many problems of traditional ORMs, such as bloated model instances, mixing business with storage logic, lack of type-safety or unpredictible queries caused e.g. by lazy loading.

It uses the Prisma schema to define application models in a declarative way. Prisma Migrate then allows to generate SQL migrations from the Prisma schema and executes them against the database. CRUD queries are provided by Prisma Client, a lightweight and entirely type-safe database client for Node.js and TypeScript.

API design & Level of abstraction

TypeORM and Prisma operate on different levels of abstraction. TypeORM is closer to mirroring SQL in its API while Prisma Client provides a higher-level abstraction that was carefully designed with the common tasks of application developers in mind. Prisma's API design heavily leans on the idea of making the right thing easy.

While Prisma Client operates on a higher-level of abstraction, it strives to expose the full power of the underlying database and lets you drop down to raw SQL at any time if your use case requires it.

The following sections examine a few examples for how Prisma's and TypeORM's APIs differ in certain scenarios and what the rationale of Prisma's API design is in these cases.

Filtering

TypeORM primarly leans on SQL operators for filtering lists or records, e.g. with the find method. Prisma on the other hand, provides a more generic set of operators that are intuitive to use. It should also be noted that, as explained in the type-safety section below, TypeORM looses type-safety in for filter queries in many scenarios.

A good example of how the filtering APIs of both TypeORM and Prisma differ is by looking at string filters. While TypeORM primarily provides the filter based on the ILike operator which comes directly from SQL, Prisma provides more specific operators that developers can use, e.g.: contains, startsWith and endsWith.

Prisma
const posts = await postRepository.find({
where: {
title: "Hello World"
},
});
TypeORM
const posts = await postRepository.find({
where: {
title: ILike("Hello World")
},
});
Prisma
const posts = await postRepository.find({
where: {
title: { contains: "Hello World" }
},
});
TypeORM
const posts = await postRepository.find({
where: {
title: ILike("%Hello World%")
},
});
Prisma
const posts = await postRepository.find({
where: {
title: { startsWith: "Hello World" }
},
});
TypeORM
const posts = await postRepository.find({
where: {
title: ILike("Hello World%")
},
});
Prisma
const posts = await postRepository.find({
where: {
title: { endsWith: "Hello World" }
},
});
TypeORM
const posts = await postRepository.find({
where: {
title: ILike("%Hello World")
},
});

Pagination

TypeORM only offers limit-offset pagination while Prisma conveniently provides dedicated APIs for both limit-offset but also cursor-based. You can learn more about both approaches in the Pagination section of the docs or in the API comparison below.

Transactions

Long-running database transactions often are a major source of complexity in server-side applications. In many scenarios, they turn out to be footguns that are the cause of major performance bottlenecks and subtle bugs. They're also not a pattern that works well with modern deployment techniques like serverless functions since they require holding a stateful connection between the application server and its database.

While TypeORM provides a low-level transaction API that directly exposes SQL transactions to the application developer, Prisma offers dedicated APIs that handle the use cases long-running transactions are commonly (mis)used for:

  • nested writes for creating and updating relationships (docs)
  • the $transaction API for sending multiple operations in bulk (docs)
  • atomic operators for updating values in a single database query (docs)
  • application-level optimistic concurrency control (OCC) for making writes conditional on previously read data (coming soon)

You can learn more about the rationale behind these techniques and Prisma's transaction patterns in this blog post: How Prisma Supports Database Transactions.

Relations

Working with records that are connected via foreign keys can become very complex in SQL. Prisma's concept of virtual relation field enables an intuitive and convenient way for application developers to work with related data. Some benefits of Prisma's approach are:

  • traversing relationships via the fluent API (docs)
  • nested writes that enable updating/creating connected records (docs)
  • applying filters on related records (docs)
  • easy and type-safe querying of nested data without worrying about JOINs (docs)
  • creating nested TypeScript typings based on models and their relations (docs)
  • intuitive modeling of relations in the data model via relation fields (docs)
  • implicit handling of relation tables (also sometimes called JOIN, link, privot or junction tables) (docs)

Data modeling and migrations

Prisma models are defined in the Prisma schema while TypeORM uses classes and experimental TypeScript decorators for model definitions. With the Active Record ORM pattern, TypeORM's approach often leads to complex model instances that are becoming hard to maintain as an application grows.

Prisma on the other hand generates a lightweight database client that exposes a tailored and fully type-safe API to read and write data for the models that are defined in the Prisma schema, following the DataMapper ORM pattern rather than Active Record.

Prisma's DSL for data modeling is lean, simple and intuitive to use. When modeling data in VS Code, you can further take advantage of Prisma's powerful VS Code extension with features like autocompletion, quick fixes, jump to definition and other benefits that increase developer productivity.

Prisma
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
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])
}
TypeORM
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToMany,
ManyToOne
} from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ nullable: true })
name: string;
@Column({ unique: true })
email: string;
@OneToMany((type) => Post, (post) => post.author)
posts: Post[];
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column({ nullable: true })
content: string;
@Column({ default: false })
published: boolean;
@ManyToOne((type) => User, (user) => user.posts)
author: User;
}

Migrations work in similar fashions in TypeORM and Prisma. Both tools follow the approach of generating SQL files based on the provided model definitions and provide a CLI to execute them against the database. The SQL files can be modified before the migrations are executed so that any custom database operation can be performed with either migration system.

Type safety

TypeORM has been one of the first ORMs in the Node.js ecosystem to fully embrace TypeScript and has done a great job in enabling developers to get a certain level of type safety for their database queries.

However, there are numerous situations where the type safety guarantees of TypeORM fall short. The following sections describe the scenarios where Prisma can provide stronger guarantees for the types of query results.

Selecting fields

This section explains the differences in type safety when selecting a subset of a model's fields in a query.

TypeORM

TypeORM provides a select option for its find methods (e.g. find, findByIds, findOne, ...), for example:

`find` with `select`
Model
const postRepository = getManager().getRepository(Post);
const publishedPosts: Post[] = await postRepository.find({
where: { published: true },
select: ["id", "title"]
});

While each object in the returned publishedPosts array only carries the selected id and title properties at runtime, the TypeScript compiler doesn't have any knowledge of this. It will allow you to access any other properties defined on the Post entity after the query, for example:

const post = publishedPost[0]
// The TypeScript compiler has no issue with this
if (post.content.length > 0) {
console.log(`This post has some content.`)
}

This code will result in an error at runtime:

TypeError: Cannot read property 'length' of undefined

The TypeScript compiler only sees the Post type of the returned objects, but it doesn't know about the fields that these objects actually carry at runtime. It therefore can't protect you from accessing fields that have not been retrieved in the database query, resulting in a runtime error.

Prisma

Prisma Client can guarantee full type safety in the same situation and protects you from accessing fields that were not retrieved from the database.

Consider the same example with a Prisma Client query:

`findMany` with `select`
Model
const publishedPosts = await prisma.post.findMany({
where: { published: true },
select: {
id: true,
title: true
}
})
const post = publishedPosts[0]
// The TypeScript compiler will not allow this
if (post.content.length > 0) {
console.log(`This post has some content.`)
}

In this case, the TypeScript compiler will throw the following error already at compile-time:

[ERROR] 14:03:39 ⨯ Unable to compile TypeScript:
src/index.ts:36:12 - error TS2339: Property 'content' does not exist on type '{ id: number; title: string; }'.
42 if (post.content.length > 0) {

This is because Prisma Client generates the return type for its queries on the fly. In this case, publishedPosts is typed as follows:

const publishedPosts: {
id: number;
title: string;
}[]

It therefore is impossible for you to accidentally access a property on a model that has not been retrieved in a query.

Loading relations

This section explains the differences in type safety when loading relations of a model in a query. In traditional ORMs, this is sometimes called eager loading.

TypeORM

TypeORM allows to eagerly load relations from the database via the relations option that can be passed to its find methods.

Consider this example:

`find` with `relations`
Models
const postRepository = getManager().getRepository(Post);
const publishedPosts: Post[] = await postRepository.find({
where: { published: true },
relations: ["author"]
});

Unlike with select, TypeORM does not provide autocompletion, nor any type-safety for the strings that are passed to the relations option. This means, the TypeScript compiler is not able to catch any typos that are made when querying these relations. For example, it would allow for the following query:

const publishedPosts: Post[] = await postRepository.find({
where: { published: true },
// this query would lead to a runtime error because of a typo
relations: ["authors"]
});

This subtle typo would now lead to the following runtime error:

UnhandledPromiseRejectionWarning: Error: Relation "authors" was not found; please check if it is correct and really exists in your entity.

Prisma

Prisma protects you from mistakes like this and thus eliminates a whole class of errors that can occur in your application at runtime. When using include to load a relation in a Prisma Client query, you can not only take advantage of autocompletion to specify the query, but the result of the query will also be properly typed:

`find` with `relations`
Models
const publishedPosts = await prisma.post.findMany({
where: { published: true },
include: { author: true },
});

Again, the type of publishedPosts is generated on the fly and looks as follows:

const publishedPosts: (Post & {
author: User;
})[]

For reference, this is what the User and Post types look like that Prisma Client generates for your Prisma models:

`User`
`Post`
// Generated by Prisma
export type User = {
id: number
name: string | null
email: string
}

Filtering

This section explains the differences in type safety when filtering a list of records using where.

TypeORM

TypeORM allows to pass a where option to its find methods to filter the list of returned records according to specific criteria. These criteria can be defined with respect to a model's properties.

Loosing type-safety using operators

Consider this example:

`find` with `select`
Model
const postRepository = getManager().getRepository(Post);
const publishedPosts: Post[] = await postRepository.find({
where: {
published: true,
title: ILike("Hello World"),
views: MoreThan(0)
},
});

This code runs properly and produces a valid query at runtime. However, the where option is not really type-safe in various different scenarios. When using a FindOperator like ILike or MoreThan that only work for specific types (ILike works for strings, MoreThan for numbers), you're losing the guarantee of providing the correct type for the model's field.

For example, you can provide a string to the MoreThan operator. The TypeScript compiler will not complain and your application will only fail at runtime:

const postRepository = getManager().getRepository(Post);
const publishedPosts: Post[] = await postRepository.find({
where: {
published: true,
title: ILike("Hello World"),
views: MoreThan("test")
},
});

The code above results in a runtime error that the TypeScript compiler doesn't catch for you:

error: error: invalid input syntax for type integer: "Hello World"
Specifying non-existing properties

Also note that the TypeScript compiler allows you to specify properties on the where option that don't exist on your models – again resulting in runtime errors:

const publishedPosts: Post[] = await postRepository.find({
where: {
published: true,
title: ILike("Hello World"),
viewCount: 1
},
});

In this case, your application again fails at runtime with the following error:

EntityColumnNotFound: No entity column "viewCount" was found.

Prisma

Both filtering scenarios that are problematic with TypeORM in terms of type-safety are covered by Prisma in a fully type-safe way.

Type-safe usage of operators

With Prisma, the TypeScript compiler enforces the correct usage of an operator per field:

const publishedPosts = await prisma.post.findMany({
where: {
published: true,
title: { contains: "Hello World" },
views: { gt: 0 }
},
});

It would not be allowed to specify the same problematic query shown above with Prisma Client:

const publishedPosts = await prisma.post.findMany({
where: {
published: true,
title: { contains: "Hello World" },
views: { gt: "test" } // Caught by the TypeScript compiler
},
});

The TypeScript compiler would catch this and throw the following error to protect you from a runtime failure of the app:

[ERROR] 16:13:50 ⨯ Unable to compile TypeScript:
src/index.ts:39:5 - error TS2322: Type '{ gt: string; }' is not assignable to type 'number | IntNullableFilter'.
Type '{ gt: string; }' is not assignable to type 'IntNullableFilter'.
Types of property 'gt' are incompatible.
Type 'string' is not assignable to type 'number'.
42 views: { gt: "test" }
Type-safe definition of filters as model properties

With TypeORM, you are able to specify a property on the where option that doesn't map to a model's field. In the above example, filtering for viewCount therefore led to a runtime error because the field actually is called views.

With Prisma, the TypeScript compiler will not allow to reference any properties inside of where that don't exist on the model:

const publishedPosts = await prisma.post.findMany({
where: {
published: true,
title: { contains: "Hello World" },
viewCount: { gt: 0 } // Caught by the TypeScript compiler
},
});

Again, the TypeScript compiler complains with the following message to protect your from your own mistakes:

[ERROR] 16:16:16Unable to compile TypeScript:
src/index.ts:39:5 - error TS2322: Type '{ published: boolean; title: { contains: string; }; viewCount: { gt: number; }; }' is not assignable to type 'PostWhereInput'.
Object literal may only specify known properties, and 'viewCount' does not exist in type 'PostWhereInput'.
42 viewCount: { gt: 0 }

Creating new records

This section explains the differences in type safety when creating new records.

TypeORM

With TypeORM, there are two main ways to create new records in the database: insert and save. Both methods allow developers to submit data that can lead to runtime errors when required fields are not provided.

Consider this example:

Create with `save`
Create with `insert`
Model
const userRepository = getManager().getRepository(User);
const newUser = new User()
newUser.name = "Alice"
userRepository.save(newUser)

No matter if you're using save or insert for record creation with TypeORM, you will get the following runtime error if you forget to provide the value for a required field:

QueryFailedError: null value in column "email" of relation "user" violates not-null constraint

The email field is defined as required on the User entitity (which is enforced by a NOT NULL constraint in the database).

Prisma

Prisma protects you from these kind of mistakes by enforcing that you submit values for all required fields of a model.

For example, the following attempt to create a new User where the required email field is missing would be caught by the TypeScript compiler:

Create with `create`
Model
const newUser = await prisma.user.create({
data: {
name: "Alice"
}
})

It would lead to the following compile-time error:

[ERROR] 10:39:07 ⨯ Unable to compile TypeScript:
src/index.ts:39:5 - error TS2741: Property 'email' is missing in type '{ name: string; }' but required in type 'UserCreateInput'.

API comparison

Fetching single objects

Prisma

const user = await prisma.user.findUnique({
where: {
id: 1,
},
});

TypeORM

const userRepository = getRepository(User)
const user = await userRepository.findOne(id)

Fetching selected scalars of single objects

Prisma

const user = await prisma.user.findUnique({
where: {
id: 1,
},
select: {
name: true,
},
});

TypeORM

const userRepository = getRepository(User)
const user = await userRepository.findOne(id, {
select: ["id", "email"]
})

Fetching relations

Prisma

Using include
Fluent API
const posts = await prisma.user.findUnique({
where: {
id: 2,
},
include: {
post: true,
},
});

Note: select return a user object that includes a post array, whereas the fluent API only returns a post array.

TypeORM

Using `relations`
Using JOIN
Eager relations
const userRepository = getRepository(User)
const user = await userRepository.findOne(id, {
relations: ["posts"]
})

Filtering for concrete values

Prisma

const posts = await prisma.post.findMany({
where: {
title: {
contains: "Hello",
},
},
});

TypeORM

const userRepository = getRepository(User)
const users = await userRepository.find({
where: {
name: "Alice"
}
})

Other filter criteria

Prisma

Prisma generates many additional filters that are commonly used in modern application development.

TypeORM

TypeORM provides built-in operators that can be used to create more complex comparisons

Relation filters

Prisma

Prisma lets you filter a list based on a criteria that applies not only to the models of the list being retrieved, but to a relation of that model.

For example, the following query returns users with one or more posts with "Hello" in the title:

const posts = await prisma.user.findMany({
where: {
Post: {
some: {
title: {
contains: "Hello",
},
},
},
}
});

TypeORM

TypeORM doesn't offer a dedicated API for relation filters. You can get similar functionality by using the QueryBuilder or writing the queries by hand.

Pagination

Prisma

Cursor-style pagination:

const page = prisma.post.findMany({
before: {
id: 242,
},
last: 20,
});

Offset pagination:

const cc = prisma.post.findMany({
skip: 200,
first: 20,
});

TypeORM

const postRepository = getRepository(Post)
const posts = await postRepository.find({
skip: 5,
take: 10
})

Creating objects

Prisma

const user = await prisma.user.create({
data: {
email: "alice@prisma.io",
},
});

TypeORM

Using `save`
Using `create`
Using `insert`
const user = new User()
user.name = "Alice"
user.email = "alice@prisma.io"
await user.save()

Updating objects

Prisma

const user = await prisma.user.update({
data: {
name: "Alicia",
},
where: {
id: 2
}
});

TypeORM

const userRepository = getRepository(User)
const updatedUser = await userRepository.update(id, {
name: "James",
email: "james@prisma.io"
})

Deleting objects

Prisma

const user = prisma.user.delete({
where: {
id: 10,
},
});

TypeORM

Using `delete`
Using `remove`
const userRepository = getRepository(User)
await userRepository.delete(id)

Batch updates

Prisma

const user = await prisma.user.updateMany({
data: {
name: "Published author!",
},
where: {
Post: {
some: {
published: true,
},
},
},
});

TypeORM

You can use the query builder to update entities in your database.

Batch deletes

Prisma

const users = await prisma.user.deleteMany({
where: {
id: {
in: [1, 2, 6, 6, 22, 21, 25],
},
},
});

TypeORM

Using `delete`
Using `remove`
const userRepository = getRepository(User)
await userRepository.delete([id1, id2, id3])

Transactions

Prisma

const user = await prisma.user.create({
data: {
email: "bob.rufus@prisma.io",
name: "Bob Rufus",
Post: {
create: [
{ title: "Working at Prisma" },
{ title: "All about databases" },
],
},
},
});

TypeORM

await getConnection().$transaction(async transactionalEntityManager => {
const user = getRepository(User).create({
name: "Bob",
email: "bob@prisma.io"
})
const post1 = getRepository(Post).create({
title: "Join us for GraphQL Conf in 2019",
})
const post2 = getRepository(Post).create({
title: "Subscribe to GraphQL Weekly for GraphQL news",
})
user.posts = [post1, post2]
await transactionalEntityManager.save(post1)
await transactionalEntityManager.save(post2)
await transactionalEntityManager.save(user)
})
export const _frontmatter = {"title":"TypeORM","metaTitle":"Prisma vs TypeORM","metaDescription":"Learn how Prisma compares to TypeORM."}
Edit this page on GitHub