Skip to main content

TypeORM

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

TypeORM vs Prisma ORM

While Prisma ORM 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 ORM 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 unpredictable 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 ORM 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 ORM'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 ORM's and TypeORM's APIs differ in certain scenarios and what the rationale of Prisma ORM's API design is in these cases.

Filtering

TypeORM primarily leans on SQL operators for filtering lists or records, e.g. with the find method. Prisma ORM 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 loses type-safety in filter queries in many scenarios.

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

Prisma ORM
const posts = await prisma.post.findMany({
where: {
title: 'Hello World',
},
})
TypeORM
const posts = await postRepository.find({
where: {
title: ILike('Hello World'),
},
})
Prisma ORM
const posts = await prisma.post.findMany({
where: {
title: { contains: 'Hello World' },
},
})
TypeORM
const posts = await postRepository.find({
where: {
title: ILike('%Hello World%'),
},
})
Prisma ORM
const posts = await prisma.post.findMany({
where: {
title: { startsWith: 'Hello World' },
},
})
TypeORM
const posts = await postRepository.find({
where: {
title: ILike('Hello World%'),
},
})
Prisma ORM
const posts = await prisma.post.findMany({
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 ORM 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.

Relations

Working with records that are connected via foreign keys can become very complex in SQL. Prisma ORM's concept of virtual relation field enables an intuitive and convenient way for application developers to work with related data. Some benefits of Prisma ORM'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, pivot 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 ORM 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 ORM'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 ORM's powerful VS Code extension with features like autocompletion, quick fixes, jump to definition and other benefits that increase developer productivity.

Prisma ORM
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 ORM. 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 ORM 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:

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 = publishedPosts[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 ORM

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:

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:

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 ORM

Prisma ORM 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:

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:

// Generated by Prisma ORM
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:

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: "test"
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 ORM

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

Type-safe usage of operators

With Prisma ORM, 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 ORM, 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 you from your own mistakes:

[ERROR] 16:16:16 ⨯ Unable 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:

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 entity (which is enforced by a NOT NULL constraint in the database).

Prisma ORM

Prisma ORM 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:

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 ORM

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 ORM

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 ORM

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

const userRepository = getRepository(User)
const user = await userRepository.findOne(id, {
relations: ['posts'],
})

Filtering for concrete values

Prisma ORM

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 ORM

Prisma ORM 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 ORM

Prisma ORM 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 ORM

Cursor-style pagination:

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

Offset pagination:

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

TypeORM

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

Creating objects

Prisma ORM

const user = await prisma.user.create({
data: {
email: '[email protected]',
},
})

TypeORM

const user = new User()
user.name = 'Alice'
user.email = '[email protected]'
await user.save()

Updating objects

Prisma ORM

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: '[email protected]',
})

Deleting objects

Prisma ORM

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

TypeORM

const userRepository = getRepository(User)
await userRepository.delete(id)

Batch updates

Prisma ORM

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 ORM

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

TypeORM

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

Transactions

Prisma ORM

const user = await prisma.user.create({
data: {
email: '[email protected]',
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: '[email protected]',
})
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)
})