Prisma - установка в Nest-проекте

Установка Prisma выполняется очень просто - при помощи двух команд. Первая - устанавливает клиентскую часть Prisma:

$ npm i @prisma/client

… вторая команда - устанавливает cli-часть Prisma:

$ npm i -D prisma

После этого - нужно запустить команду для инициализации Prisma в текущем проекте:

$ npx prisma init

В результате в корне проекта будет создана папка prisma, внутри которой будет находиться файл schema.prisma для настройки подключения Prisma к базе данных (и не только это).

Содержимое файла очень простое:

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

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

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

Наиболее существенные здесь две вещи - это драйвер postgresql для подключения к базе данных Postgres (по умолчанию используется подключение именно к Postgres). Если база данных другая - MySQL и тп - то меняем на нужный драйвер.

Вторая - это строка url, которая используется как настроечный файл для подключения к базе данных. Эта строка (в виде переменной DATABASE_URL) хранится в файле .env, который также автоматически создается Prisma при инициализации.

Если посмотреть на содержимое файла .env, то он будет таким:

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB (Preview) and CockroachDB (Preview).
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

То есть - в одной строке - выполняется полная настройка подключения к существующей базе данных. В моем учебном случае это выглядит таким образом:

DATABASE_URL="postgresql://postgres:123@localhost:5432/udemy_medium_clone?schema=public"

То есть - postgres - имя пользователя базы данных; 123 - пароль пользователя базы данных; udemy_medium_clone - имя базы данных.

Откуда я взял\узнал эти данные? Просто я заранее создал учебную базу данных в Postgres, у себя локально (в моем случае):

postgres=# CREATE DATABASE udemy_medium_clone;
CREATE DATABASE
postgres=# \c udemy_medium_clone 
You are now connected to database "udemy_medium_clone" as user "postgres".
udemy_medium_clone=# \conninfo
You are connected to database "udemy_medium_clone" as user "postgres" via socket in "/var/run/postgresql" at port "5432".

localhost:5432 - я оставил по умолчанию, так как - у меня Postgres также по умолчанию слушает этот порт.

Не забываем добавить файл .env в .gitignore, чтобы данные не засветились на удаленном репозитории - и все, основная настройка Prisma завершена.

Prisma - миграция баз данных

В моем случае - база данных udemy_medium_clone - чистая, в ней пока нет никаких таблиц. Это можно исправить двумя способами.

Первый - создать таблицу в базе данных силами самого SQL и затем выполнить преобразование таблицы в модель данных (Prisma schema) в проекте - командой:

prisma db pull

Второй - наоборот, создать модель данных Prisma в проекте и затем мигрировать ее в Postgres, что автоматически приведет к созданию такой таблицы в базе данных.

Давайте поступим по второму варианту. Создадим в файле schema.prisma модель данных пользователя будущего приложения:

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

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

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

model Users {
  id       Int     @id @default(autoincrement())
  email    String  @unique @db.VarChar(255)
  bio      String?
  image    String? @db.VarChar(500)
  password String  @unique @db.VarChar(255)
  username String  @unique @db.VarChar(255)

  @@map("users")
}

(кстати - под VSC и WS - есть специальные плагины для подсветки синтаксиса и форматирования файлов с расширением *.prisma; для VSC плагин работает отлично, для WS - так себе).

И все - можно выполнить миграцию в базу данных командой:

npx prisma migrate dev --name users

… где –name users - это имя конкретной миграции, чтобы можно было локально увидеть и отследить эту операцию. По выполнении Prisma выведет в консоль отчет об успешной операции:

Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "udemy_medium_clone", schema "public" at "localhost:5432"

Applying migration `20220223160632_users`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20220223160632_users/
    └─ migration.sql

Your database is now in sync with your schema.

✔ Generated Prisma Client (3.10.0 | library) to ./node_modules/@prisma/client in 75ms

Как видно из отчета, Prisma создала подпапку migrations, внутри которой будут находиться - папки с детальной информацией по каждой конкретной миграции.

В моем случае - это будет папка 20220223160632_users (помним о ключе –name users) и внутри этой папки - любопытный файлик migration.sql. Если открыть этот файлик, то увидим не что иное, как набор обычных sql-команд по созданию таблицы базы данных:

-- CreateTable
CREATE TABLE "users" (
    "id" SERIAL NOT NULL,
    "email" VARCHAR(255) NOT NULL,
    "bio" TEXT,
    "image" VARCHAR(500),
    "password" VARCHAR(255) NOT NULL,
    "username" VARCHAR(255) NOT NULL,

    CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");

-- CreateIndex
CREATE UNIQUE INDEX "users_password_key" ON "users"("password");

-- CreateIndex
CREATE UNIQUE INDEX "users_username_key" ON "users"("username");

То есть, по факту - сперва была выполнена операция по преобразованию Prisma-модели в такой sql-запрос, а уже потом - этот запрос выполнился и была создана таблица в базе данных.

А давайте проверим, так ли это на самом деле? Легко!

udemy_medium_clone=# \d
                 List of relations
 Schema |        Name        |   Type   |  Owner   
--------+--------------------+----------+----------
 public | _prisma_migrations | table    | postgres
 public | users              | table    | postgres
 public | users_id_seq       | sequence | postgres
(3 rows)

… правда - таблица users была создана. А давайте посмотрим - что из себя представляет эта таблица users:

udemy_medium_clone=# \d users
                                     Table "public.users"
  Column  |          Type          | Collation | Nullable |              Default              
----------+------------------------+-----------+----------+-----------------------------------
 id       | integer                |           | not null | nextval('users_id_seq'::regclass)
 email    | character varying(255) |           | not null | 
 bio      | text                   |           |          | 
 image    | character varying(500) |           |          | 
 password | character varying(255) |           | not null | 
 username | character varying(255) |           | not null | 
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "users_email_key" UNIQUE, btree (email)
    "users_password_key" UNIQUE, btree (password)
    "users_username_key" UNIQUE, btree (username)

… хм, очень похоже на правду - это именно такая таблица, какую я спроектировал в качестве модели в Prisma.

Заключение

Ну вот в принципе и все - Prisma установлена в проекте, успешно настроено ее подключение к конкретной базе данных; создана модель данных и выполнена успешная миграция ее в таблицу в базе данных. Prisma успешно подключена и готова к работе.

Конечно же, есть еще один инструмент для работы с базой данных в Next - это TypeORM. Но его настройка и подключение для меня настолько сложна, запутанна и трудоемка, что я не стыжусь признаться, что TypeORM я не знаю и особого желания знать пока не возникло.


После того, как CMUS успешно установлен, хорошо было бы - изменить тему оформления - с той, которая есть по умолчанию.

Это легко сделать. При установке CMUS - устанавливаются также дополнительные темы оформления.

Находятся они по пути /usr/share/cmus и можно их посмотреть так:

ls /usr/share/cmus

Также список тем можно посмотреть в репозитории пакета, на GitHub - CMUS Themes.

Изменить тему оформления можно командой:

:colorscheme gruvbox

… именно так - просто имя темы, без расширения. И все - можно пользоваться.


Как в Prisma - получать колонки выборочно

Самый простой пример получения всех данных (колонок) из таблицы в базе данных - при помощи Prisma такой:

const user = await prismaClient.users.findUnique({
    where:{
        id: userId
    }
})

… что является аналогом такой sql-команды, например:

SELECT * FROM users AS u WHERE u.user_id = 2;

Но что, если нужно получить в Prisma - только определенный набор колонок? При помощи sql-команды это делается просто:

SELECT u.user_name, u.user_gender, u.user_birthday FROM users AS u WHERE u.user_id = 2;

Как же выполнить точно такой же запрос - в Prisma? Это можно сделать при помощи дополнительной опции select.

В принципе - в оф. доке все описано - “… select defines which fields are included in the object that Prisma Client returns …” - “… select определяет, какие поля будут включены в объект, который возвращает Prisma Client …”.

И вот пример, как это можно сделать:

const user = await prismaClient.users.findUnique({
    select:{
        user_name: true,
        user_gender: true,
        user_birthday: true,
    },
    where:{
        id: userId
    }
})

Ссылка для более детального чтения, с примерами - Model query options


Пошагово - как создать playlist в CMUS

Есть такой отличный музыкальный плейер - CMUS; прелесть его в том, что он - консольный, легковесный; висит в фоновом режиме - и играет тебе музыку. Он богат функционалом, но вот его команды - приходится запоминать.

Шаги для создания playlist и папки с музыкой\аудиокнигой. Команды у CMUS - vim-подобные, начинаются с символа двоеточия.

Создание самого playlist’а

Достаточно выполнить такую командой - в любом окне CMUS:

:pl-create my-awesome-playlist

Переход в окно с playlist’ами

Жмем кнопочку с циферкой 3 - чтобы перейти в окно, в котором отображаются все playlist’ы - они будут слева, в колонку расположены. Подводим под вновь созданный playlist курсор (arrow-keys) и жмем Space, чтобы выделить этот playlist.

Добавление файлов в playlist

Вводим команду - для добавления файлов в выделенный playlist. В моем случае - это папка с файлами аудиокниги:

:add -p ~/Music/awesome-audiobook/

Единственное неудобство - CMUS не понимает пути к файлам, если они написаны кириллицей, не латиницей; приходится заранее - переименовывать папку, перед ее добавлением в playlist CMUS. По крайней мере - я пока не нашел, как заставить CMUS понимать кириллицу в путях.

Когда команда выполнится - в правой колонке окна под номером 3 - появится список аудио-файлов, которые привязаны к данному playlist’у. Можно переходить курсором на этот список - и пользоваться.


Пользовательское объединение типов - что это и как можно использовать

Помимо объединения примитивных типов данных (например):

type myType = number | string;

… в TypeScript можно делать и объединение пользовательских типов данных:

interface Rectange {
  width: number,
  height: number
}

interface Circle {
  radius: number
}

type Shape = Rectange | Circle;

Более того, пользовательские типы можно связать между собой при помощи общего для всех типов поля:

interface Rectange {
  width: number,
  height: number,
  type: 'rectange'
}

interface Circle {
  radius: number,
  type: 'circle'
}

type Shape = Rectange | Circle;

… здесь поле type - является таким связующим звеном; такое поле называется дискриминантом.

Прелесть такого объединения типов заключается в том, что можно проверять значение этого поля и в зависимости от результата - выполнять нужное действие. Например, можно создать такую функцию calcArea:

function calcArea(shape: Shape): number {
  const { type } = shape;

  switch(type) {
    case 'rectange':
      return shape.width * shape.height;
    case 'circle':
      return Math.round(Math.PI * shape.radius ** 2);
  }
}

… и тогда пример использования этой функции и объединенного типа будет таким:

interface Rectange {
  width: number,
  height: number,
  type: 'rectange'
}

interface Circle {
  radius: number,
  type: 'circle'
}

type Shape = Rectange | Circle;

function calcArea(shape: Shape): number {
  const { type } = shape;

  switch(type) {
    case 'rectange':
      return shape.width * shape.height;
    case 'circle':
      return Math.round(Math.PI * shape.radius ** 2);
  }
}

const rectangle: Rectange = { width: 10, height: 10, type: 'rectange' };
const circle: Circle = { radius: 20, type: 'circle' };

console.log(calcArea(rectangle)); // => 100
console.log(calcArea(circle)); // => 1257