Prisma 2 - Como instalar prisma en un monorepo con NX para Nest.js

Prisma 2 - Como instalar prisma en un monorepo con NX para Nest.js

Prisma tal y como lo describen en su sitio web es un ORM de próxima generación para Node.js y TypeScript. Prisma ayuda a desarrollar aplicaciones más rápido y con menos errores con un ORM libre para PostgreSQL, MySQL y SQLite.

Por si os interesa os dejo info de NX y el concepto de monorepos, y un vídeo donde Beeman explica un poco más acerca del stack que usa con Angular, Nest.js, GraphQL, Apollo y Prisma en el mismo monorepo. Y aquí otro tutorial de Prisma.

Primeros pasos

Primero necesitamos el CLI de prisma, yo utilizo yarn pero sientete libre de utilizar npm si lo deseas

yarn add @prisma/cli -D

Después tenemos que inicializar prisma

npx prisma init

Esto va a crear una carpeta en nuestro proyecto con el archivo schema.prisma

Screenshot from 2021-01-02 21-31-46.png

Al abrirlo verás esto

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

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

Añadimos nuestros modelos en el mismo archivo schema.prisma

model User {
  id        String    @id @default(cuid())
  createdAt DateTime  @default(now())
  updatedAt DateTime  @default(now()) @updatedAt
  email     String    @unique
  password  String
  posts     Post[]
}

model Post {
  id        String    @id @default(cuid())
  createdAt DateTime  @default(now())
  updatedAt DateTime  @default(now()) @updatedAt
  text      String
  authorId  String
  author    User      @relation(fields: [authorId], references: [id])
}

Para poder probar prisma en local vamos a lanzar una instancia de postgres con un archivo docker-compose.yml

version: '3'
services:
  postgres:
    image: postgres
    ports:
      - 5432:5432
    environment:
      POSTGRES_DB: prisma
      POSTGRES_USER: prisma
      POSTGRES_PASSWORD: prisma
    volumes:
      - ./tmp/postgres:/var/lib/postgresql/data

Y arrancamos la base de datos en la terminal con el comando

docker-compose up

Es así de simple, visita la documentación de docker si no lo tienes instalado.

Ahora necesitamos cambiar la configuración de conexión con la base de datos en el archivo .env

DATABASE_URL="postgresql://prisma:prisma@localhost:5432/prisma?schema=public"

Hacemos la migración con prisma para crear los modelos en la base de datos.

npx prisma migrate dev --preview-feature

Esto creará un directorio de migraciones dentro de la carpeta prisma. Una vez que tenemos la migración vamos a generar el client de prisma que nos permitirá hacer las querys en la base de datos.

npx prisma generate

Opcional * Si quieres puedes añadir un script en package.json para migrar y generar el cliente cuando haces cambios

"prisma:up": "npx prisma migrate dev --preview-feature && npx prisma generate"

Ahora ya podemos usar el client de prisma

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

Si estás usando NX creamos una librería para gestionar prisma

nx g @nrwl/nest:lib data

Vamos a generar un servicio para realizar las querys al lado de data.module.ts y no olvides añadirlo en la lista de providers.

import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class DataService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor() {
    super();
  }

  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }

  async createUser({ email, password }: { email: string; password: string }) {
    const user = await this.user.create({ data: { email, password } });
    console.log(user);
  }
}

Ahora solo tenemos que importar el DataService en cualquier servicio para empezar a usar las querys de prisma. Aquí os dejo un ejemplo de un CRUD típico.

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateCategoryInput } from './dto/create.category.input';
import { CreatePostInput } from './dto/create.post.input';
import { UpdateCategoryInput } from './dto/update.category.input';
import { UpdatePostInput } from './dto/update.post.input';
import { Category } from './models/category';
import { Post } from './models/Post';
import { DataService } from '@fubu/data';

@Injectable()
export class CategoryService {
  constructor(private readonly data: DataService) {}

  public async categories(): Promise<Category[]> {
    return await this.data.category.findMany({ include: { posts: true } });
  }

  public async category(id: string): Promise<Category> {
    const found = await this.data.category.findUnique({ where: { id } });
    if (!found) throw new NotFoundException();
    return found;
  }

  public async createCategory(input: CreateCategoryInput): Promise<Category> {
    return await this.data.category.create({ data: { ...input } });
  }

  public async updateCategory(id: string, input: UpdateCategoryInput): Promise<Category> {
    const category = await this.category(id);
    return await this.data.category.update({ where: { id: category.id }, data: { ...input } });
  }

  public async deleteCategory(id: string): Promise<Category> {
    const category = await this.category(id);
    return await this.data.category.delete({ where: { id: category.id } });
  }

  public async post(id: string): Promise<Post> {
    const found = await this.data.post.findUnique({ where: { id } });
    if (!found) throw new NotFoundException();
    return found;
  }

  public async createPost(categoryId: string, input: CreatePostInput): Promise<Post> {
    const category = await this.category(categoryId);
    return await this.data.post.create({
      data: {
        Category: { connect: { id: category.id } },
        ...input,
      },
    });
  }

  public async updatePost(id: string, input: UpdatePostInput): Promise<Post> {
    const post = await this.post(id);
    return this.data.post.update({ where: { id: post.id }, data: { ...input } });
  }

  public async deletePost(id: string): Promise<Post> {
    const post = await this.post(id);
    return this.data.post.delete({ where: { id: post.id } });
  }
}

Gracias por leer el post espero que haya sido de ayuda, cualquier duda por favor comentad!! 👇👇👇