Eloquent-prisma (add laravel eloquent behaviour to prisma)

Created June 14, 2022

I have worked with Laravel and it's eloquent builder is really amazing, making the lives of developers easier in terms of database interactions. In my attempt to find an equivalent in the javascript, I found Prisma. Prisma is also a whole new world when it comes to ORM's and it allows us to do so much much more with our SQL and even NoSQL data without having to write SQL statements. I believe unless you are a database administrator or more of a database engineer that works consistently on database and it's administration, you will most likely rely in one way or the other on an ORM and in the javascript/typescript community, Prisma is definitely the best I have seen and used.

Prisma allows you to model your data and query it but it does not offer as much functionality as Laravel Eloquent to be honest. Now this does not mean that Laravel Eloquent is any better it's just that they are tools for the same purpose but different use cases altogether. Prisma, normally used with typescript and javascript projects whereas Laravel Eloquent is a core part of the laravel framework itself and has definitely seen better improvements over the years along with the framework.

I find querying my models in prisma to be quite easy but I sometimes end up with a lot of functions like getUser, getUserById, getUserComments and so on. So I decided to try mimicking Laravel eloquent.

With laravel eloquent, I can perform operations such as

// finding a user and failing if no results
User::findOrFail(1);
// getting the posts of a user
User::find(1)->posts; // or 
User::with('posts'); // to eager load the posts

// there's obviously soo much more than these examples

I don't suggest the way I discuss here as the best way to organise your models or database interactions with typescript, it's just a way I am experimenting with and sharing with you. I like to call this pattern eloquent-prisma

In this example, I setup eloquent-prisma with a user model

Setup Prisma

// lib/prisma.ts
import { PrismaClient } from '@prisma/client';

export const prisma = global.prisma || new PrismaClient();

if (process.env.NODE_ENV !== 'production') {
  global.prisma = prisma;
}

// you might need to setup support for typescript
// as prisma might throw an error with any type for the global prisma
// you can use this to resolve the type error in your types file
//env.d.ts
import type { PrismaClient } from '@prisma/client';

declare global {
  var prisma: PrismaClient | undefined;
}

Add a User model

import type { Prisma } from '@prisma/client';
import { prisma as client } from 'lib/prisma';

class User{
 constructor(public data: Prisma.UserCreateInput) {}

 async save() {
   return await User.create(this.data);
 }

 static async create(data: Prisma.UserCreateInput) {
   return await client.user.create({
     data: data,
   });
 }

 static async updateOrCreate(data: Prisma.UserCreateInput) {
   const {id, ...user} = data;
   return await client.user.upsert({
     where: {
       id,
       email: user.email!
     },
     update: user,
     create: user,
   });
 }

 static async find(id: string) {
   return await client.user.findFirst({
     where: {
       id,
     },
   });
 }

 static async findOrFail(id: string) {
   try {
     const user = await this.find(id);
     if (!user) {
       throw new Error('This User record could not found');
     }
     return user;
   } catch (error) {
     throw error;
   }
 }

 static async destroy(id: string) {
   return await client.user.delete({
     where: {
       id,
     },
   });
 }
}

export { User };

Some Explanations and examples

const user = new User({name: 'John doe', email: 'john@doe.com'});
user.save();

Of course we can go ahead to replicate a lot of the functionalities of laravel eloquent and that is where it might be worthwhile to look into extending a base class Model for example and then implementing the various methods or adding a template for how they will work there.