What are ORMs? & Understanding Prisma and its Implementing

Official Definition

ORM stands for Object-Relational Mapping, a programming technique used in software development to convert data between incompatible type systems in object-oriented programming languages. This technique creates a "virtual object database" that can be used within the programming language.

In simpler terms, ORMs abstract the complexities of interacting with the underlying database, allowing developers to work with data using familiar object-oriented paradigms instead of raw SQL.


Straightforward Definition

ORMs let you interact with your database in a straightforward way without worrying too much about SQL or other database-specific syntax.


Why Use ORMs?

1. Simpler Syntax

ORMs provide an intuitive, readable syntax for querying and manipulating data, minimizing the need to write raw SQL queries.

2. Database Abstraction

ORMs enable database independence, allowing you to switch between different database systems (e.g., PostgreSQL, MySQL, MongoDB) with minimal changes. Typically, you only need to update the database connection string or configuration.

3. Type Safety and Autocompletion

Modern ORMs (e.g., Prisma) provide type-safe APIs and autocompletion in your IDE, reducing errors and improving developer productivity.

4. Automatic Migrations

Keeping track of changes to your database schema can be challenging in larger projects. ORMs like Prisma simplify this with automated migrations. They track schema changes, generate migration scripts, and ensure that your database schema matches your application’s data model.


What is Prisma?

Prisma is an advanced ORM that enhances the developer experience when working with databases. With features like an intuitive data model, automated migrations, type safety, and autocompletion, Prisma significantly streamlines database interactions.

Workflow

Key Features of Prisma

1. Intuitive Data Modeling

In Prisma, you define your database schema in a single file. This schema includes details such as:

  • Tables

  • Fields for each table

  • Relationships between tables (e.g., one-to-one, one-to-many)

Here’s an example of a Prisma schema:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
model User {
  id         Int      @id @default(autoincrement())
  email      String   @unique
  username   String   @unique
  password   String
  fullname  String
  phone     Int?
  todos      Todos[]
}

model Todos {
  id         Int      @id @default(autoincrement())
  title   String
  description   String
  done  Boolean @default(false)
  user_id Int
  user User @relation(fields: [user_id], references: [id])
}

This schema defines two tables (User and Post) and their relationship (one user can have many posts).


2. Automated Migrations

Prisma’s migration system helps you manage changes to your database schema seamlessly. When you modify your data model, Prisma generates migration files to update the database structure accordingly. This ensures consistency between your application code and database schema.
Refer to the diagram above for a better understanding.


Defining Relationships with Prisma

Prisma simplifies defining and managing relationships between tables. It supports the following types of relationships:

1. One-to-One

Each record in Table A is related to one record in Table B. Example: A user profile linked to a user account.

2. One-to-Many

A record in Table A can have multiple related records in Table B. Example: A user having multiple posts.

3. Many-to-One

This is the inverse of one-to-many. Example: Multiple posts belonging to a single user.

4. Many-to-Many

Records in Table A can be associated with multiple records in Table B, and vice versa. Example: Users belonging to multiple groups.

e.g., In the TODO app, there is a one-to-many relationship

Generating the Prisma Client

The Prisma Client is an auto-generated library tailored to your data model. It provides a type-safe API to interact with your database. Refer to the diagram above for a better understanding. For example:

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

// Inserting data into the database
async function insertUser(
  email: string,
  username: string,
  password: string,
  fullname: string
) {
  const res = await prisma.user.create({
    data: {
      email,
      username,
      password,
      fullname,
    },
    select: {
      id: true,
      email: true,
      username: true,
      password: true,
      fullname: true,
    },
  });
  console.log(res);
}

// Updating data in the database

async function updateUser(
  id: number,
  email: string,
  username: string,
  password: string,
  fullname: string
) {
  const res = await prisma.user.update({
    where: {
      id,
    },
    data: {
      email,
      username,
      password,
      fullname,
    },
    select: {
      id: true,
      email: true,
      username: true,
      password: true,
      fullname: true,
    },
  });
  console.log(res);
}

// Getting data from the database

async function getUser(username: string) {
  const user = await prisma.user.findFirst({
    where: {
      username: username,
    },
  });
  console.log(user);
}
getUser("AmanOG");

insertUser("jaman5763@gmail.com", "Aman", "123456", "Aman Jaiswal")

updateUser(2, "jaman@gmail.com", "AmanOG", "123456", "Aman Jaiswal")

// Relationship

async function createTodo(user_id: number, title: string, description: string) {
  const todo = await prisma.todos.create({
    data: {
      title,
      description,
      user_id,
    },
  });
  console.log(todo);
}

createTodo(1, "go to gym", "go to gym and do 10 pushups");

async function getTodos(user_id: number) {
  const todos = await prisma.todos.findMany({
    where: {
      user_id: user_id,
    },
  });
  console.log(todos);
}

getTodos(1);

// one way to do this is to get user details and then get todos of that user

async function getTodosAndUserDetails(user_Id: number) {
    const user = await prisma.user.findUnique({
        where: {
            id: user_Id
        }
    });
    const todos = await prisma.todos.findMany({
        where: {
            user_id: user_Id,
        }
    });
    console.log(todos);
    console.log(user)
}

getTodosAndUserDetails(1);

// better way to do this using joins

async function getTodosAndUserDetailss(user_id: number, ) {
    const todos = await prisma.todos.findMany({
        where: {
            id: user_id,
        },
        select: {
            user: true,
            title: true,
            description: true
        }
    });
    console.log(todos);
}

getTodosAndUserDetailss(1);

For code checkout, visit: https://github.com/JAmanOG/Tried-Prisma