Journey from Mongoose to Prisma ORM for MongoDB!

somsubhra1

Somsubhra Das

Posted on July 29, 2021

Journey from Mongoose to Prisma ORM for MongoDB!

Prisma is a next Generation ORM for NodeJS & TypeScript Environments. It has multiple databases support such as MySQL, SQLite, PostgreSQL, MSSQL and also MongoDB.

Prisma Application Lifecycle
Source: Prisma

So in this post let's talk about the all new Prisma MongoDB Connector, it's operations and what made me switch from Mongoose to Prisma for MongoDB.

Connection

Let's start by establishing the connection to our MongoDB Server. In your Prisma schema file we need to change the provider.

datasource db {
  provider = "mongodb"
  url      = env("DATABASE_URL")
}
Enter fullscreen mode Exit fullscreen mode

The prisma.schema file allows us to specify how we want Prisma to connect to our database. We need to tell it what kind of provider we would like to use - in this case mongodb - and a url to connect to - this is pointing to an environment variable as we want to keep it secret. We will use a MongoDB connection string as the DATABASE_URL which can be found in the /prisma/.env file.

Next we need to setup the generator block like below.

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["mongoDb"]
}
Enter fullscreen mode Exit fullscreen mode

Since Prisma MongoDB Connector is still in preview we need to explicitly specify the previewFeatures key.

Defining Models

So now that connection has been successfully established to MongoDB Server, let's now create models for our database collection.

A typical MongoDB document looks like this:

{
  "_id": { "$oid": "60d599cb001ef98000f2cad2" },
  "email": "somsubhra@email.com",
  "name": "Somsubhra",
}
Enter fullscreen mode Exit fullscreen mode

So now how to define a model like this in Prisma? Inside Prisma schema file, we can define our models.

model User {
    id    String  @id @default(dbgenerated()) @map("_id") @db.ObjectId
    email String  @unique
    name  String?
    posts Post[]
}

model Post {
    id        String  @id @default(dbgenerated()) @map("_id")
    published Boolean @default(false)
    title     String
    user      User?   @relation(fields: [userId], references: [id])
    userId    String?
}
Enter fullscreen mode Exit fullscreen mode

Comparing it with mongoose models, in Mongoose ODM we would have written something like:

const mongoose = require("mongoose");

const Schema = mongoose.Schema;

const UserSchema = new Schema({
  name: String,
  email: {
    type: String,
    unique: true,
  },
});

module.exports = User = mongoose.model("user", UserSchema);
Enter fullscreen mode Exit fullscreen mode
const mongoose = require("mongoose");

const Schema = mongoose.Schema;

const PostSchema = new Schema({
  title: String,
  user: {
    type: mongoose.Types.ObjectId,
    ref: "user",
  },
  published: {
    type: Boolean
    default: false,
  },
});

module.exports = Post = mongoose.model("post", PostSchema);
Enter fullscreen mode Exit fullscreen mode

In Prisma we may also generate ObjectIds manually by using the bson package.

import { ObjectId } from "bson";

const id = new ObjectId();
Enter fullscreen mode Exit fullscreen mode

Queries & Database Operations

Now let's understand how to write queries and operations to MongoDB using Prisma in comparison to Mongoose.

Fetching Single record

Fetching a single record in prisma is done using the where property but in mongoose it has findById method.

Prisma

const user = await prisma.user.findUnique({
  where: {
    id: ObjectId('5eb9354624286a04e42401d8'),
  },
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

const result = await User.findById('5eb9354624286a04e42401d8')
Enter fullscreen mode Exit fullscreen mode

Fetching selected values for single record

Fetching selected values for single record is easier than ever in Prisma ORM by using just a single query function to do the select operation whereas in Mongoose after finding the record we need to chain the output with select(). This increases the time complexity and also slows down the process.

Prisma

const user = await prisma.user.findUnique({
  where: {
    id: ObjectId('5eb9354624286a04e42401d8'),
  },
  select: {
    name: true,
  },
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

const user = await User.findById('5eb9354624286a04e42401d8').select(['name', 'email'])
Enter fullscreen mode Exit fullscreen mode

Fetching relations

In Prisma, we use the include property but in Mongoose we would have to use the populate() method.

Prisma

const posts = await prisma.user.findUnique({
  where: {
    id: ObjectId('5eb9354624286a04e42401d8'),
  },
  include: {
    post: true,
  },
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

const userWithPosts = await User.findById(id).populate('posts')
Enter fullscreen mode Exit fullscreen mode

Filtering with values

In Prisma we filter records using the where property whereas in Mongoose we use the find().

Prisma

const posts = await prisma.post.findMany({
  where: {
    title: {
      contains: 'Hello World',
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

const post = await Post.find({
  title: 'Hello World',
})
Enter fullscreen mode Exit fullscreen mode

Relation Filtering

Prisma

Prisma can 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.

const posts = await prisma.user.findMany({
  where: {
    posts: {
      some: {
        title: {
          contains: 'Hello',
        },
      },
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

Mongoose doesn't offer this feature for relation filters. We may achieve similar functionality by adding an additional step to filter the results returned by the query but that would result in increased query times and load for large databases.

Pagination

Prisma

Cursor-style pagination:

const page = prisma.post.findMany({
  before: {
    id: ObjectId('6eb93546f4w5486a04e42401d8'),
  },
  last: 20,
})
Enter fullscreen mode Exit fullscreen mode

Offset pagination:

const cc = prisma.post.findMany({
  skip: 200,
  first: 20,
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

Mongoose also has similar implementation for pagination.

const posts = await Post.find({
  skip: 5,
  limit: 10,
})
Enter fullscreen mode Exit fullscreen mode

Creating Records

Prisma

const user = await prisma.user.create({
  data: {
    email: 'user@email.com',
  },
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

const user = await User.create({
  name: 'John',
  email: 'user@email.com',
})
Enter fullscreen mode Exit fullscreen mode

Updating Records

Prisma updates the record directly with the values passed in data property in comparison with mongoose where we need to use $set operator.

Prisma

const user = await prisma.user.update({
  data: {
    name: 'John',
  },
  where: {
    id: ObjectId('5eb9354624286a04e42401d8'),
  },
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

const updatedUser = await User.findOneAndUpdate(
  { _id: id },
  {
    $set: {
      name: 'John',
    },
  }
)
Enter fullscreen mode Exit fullscreen mode

Deleting Single Record

Prisma

const user = await prisma.user.delete({
  where: {
    id: ObjectId('5eb9354624286a04e42401d8'),
  },
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

await User.findByIdAndDelete(id)
Enter fullscreen mode Exit fullscreen mode

Deleting Multiple Records

Prisma

const users = await prisma.user.deleteMany({
  where: {
    id: {
      in: [1, 2, 6, 34],
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

Mongoose

await User.deleteMany({ _id: { $in: [1, 2, 6, 34] } })
Enter fullscreen mode Exit fullscreen mode

Advantages of Prisma over Mongoose

So now that we know the differences in operations between Prisma & Mongoose, let's now focus on the advantages Prisma provides over Mongoose.

  • Prisma allows TypeSafe Database access.
  • Prisma has an auto generated query builder
  • Support for multiple databases. This is a huge advantage to developers when moving between SQL and NoSQL Databases, since only the Schema file needs to be changed. All other operations/queries remain same.
  • Supports multiple RDBMS
  • 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. Mongoose doesn't offer a dedicated API for relation filters. You can get similar functionality by adding an additional step to filter the results returned by the query.
  • Prisma Studio tool that helps to manage data easily.

Disadvantages of Prisma

On the other side, Prisma has a few disadvantages over Mongoose as well.

  • Support for multiple model files not available. All models need to be written in schema.prisma file which makes the file cluttered and hard to debug and read.
  • Prisma MongoDB support is currently in preview
  • Currently no embedded collection support.
  • Error handling is incomplete.
  • The Migrate and Introspection workflows are currently not supported.
  • @@id and auto-increment are not currently supported.

Should you be using Prisma over Mongoose?

Prisma is a modern ORM which has its own tradeoffs. If you are building a server side application with REST APIs and GraphQL, Prisma would be a great choice. It also makes lives of developers easier. Prisma gives a significant productivity boost for the most common database workflows.

If these factors don't matter much to you and you want to have more control over database operations then my suggestion will be to go with Mongoose "for the time being".

Contribute to Prisma

Prisma MongoDB Connector is still in preview and development. If you want to contribute to the Prisma check out their GitHub Repository by clicking here.

💖 💪 🙅 🚩
somsubhra1
Somsubhra Das

Posted on July 29, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related