Prisma Crash Course - Simplify Your Work With Prisma Client
Prisma Crash Course - Simplify Your Work With Prisma Client

In this video I prepared for you a Prisma crash course where you will learn everything that you need to start working with Prisma on a real example.

What is Prisma?

The first question is what is Prisma at all.

website

Here is the official website of Prisma and you can see that it is an ORM for NodeJS and Typescript. What we are getting there is a data model, migrations, type safety and code completion.

The main question before we even start installing it is what databases are supported there.

databases

As you can see we can work with Postgresql, Mysql, Mariadb, Sqlite, MongoDb and others. Which actually means that amount is quite big but mostly it is being focused on relational databases. Yes it can work with MongoDB but I can't really say that I'm satisfied with the result or with code. Prisma shines really only with relational databases.

The project

Here I already prepared a small project for us.

{
  "compilerOptions": {
    "lib": ["esnext"],
    "module": "CommonJS",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  }
}

Here we have a tsconfig.json which has our Typescript configuration. We need this because we will write Typescript code inside Node.

For our application we want to use Express together with Typescript and Prisma.

npm i express nodemon ts-node @types/express

We installed here nodemon to reload our application after every save. As we are writing Typescript nodemon needs ts-node as a dependency. We also installed types for Express to get a good autocomplete.

"scripts": {
  "dev": "nodemon src/index.ts"
}

Here we added our script which starts an application.

Setting up Prisma

Our next step will be to generate initial files for Prisma. We will do that with the help of Prisma CLI tool.

npx prisma init --datasource-provider sqlite

It will generate for us a Prisma config with a database sqlite. You can provide inside Postgresql, Mongodb or anything else.

prisma init

Here we get a message that our config file was created in prisma/schema.prisma. Also we get a warning that we must add to our .gitignore file .env. Why do we need that? It created for us .env file with a link to our database.

This is how our schema.prisma file looks like.

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

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

As you can see our database is set to sqlite and the url to it is coming from environment variable. So it is not written directly in file. It is being configured. Let's look on .env file now.

DATABASE_URL="file:./dev.db"

Here inside there is a path where our sqlite database is situated. If you are using Mongodb or Postgresql you will write here a url to your database.

Also we need to check that in .gitignore we have our env file.

node_modules
.env

This looks just fine.

Creating schema

The next thing that we must do is to write some schema inside Prisma. Inside the schema we want to specify all our entities with which we want to work. Let's say that we have a project with users, posts, popular tags. We need to create all of them here in schema.prisma file. After this Prisma will generate for you table and will manage all this entities.

Prisma config is an only source of truth when we are talking about Prisma

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

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

model User {
  id Int @id @default(autoincrement())
  email String @unique
  username String
}

Here we added a User entity where id is an auto incremented Int, our email field must be unique and our username is just a string. This is how we define an entity. This is exactly how it will be stored inside database.

Generating migration

The next step that we want to do is generate a migration. Migration is an extremely important and safe thing which allows you to create snapshots of your table. For example our first snapshot of the table schema will contain exactly this User table with these fields. Every single change to our schema will lead to creating a new migrations. We are doing that to see exactly how database schema is changed.

npx prisma migrate dev --name added_user

Here we will create a new migration for development with name added_user. The name here defines exactly what we are doing inside this migration.

migration

Here we get lots of information about what is happening. As you can see it loads a config file and database url and generates a migration (a difference between the existing database and a schema) and save it.

-- /prisma/migrations/20231116202820_added_user/migration.sql
-- CreateTable
CREATE TABLE "User" (
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "email" TEXT NOT NULL,
    "username" TEXT NOT NULL
);

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

As you can see this is a typical SQL code to create a table with the fields that we defined.

app

Also Prisma created for us dev.db database file which we can open with any tool. I used here DB Browser for Sqlite. You can find it for any operational system and this is how our database looks like. You can see that we really have a User table with id, email and username.

Seeding the database

The last thing that we want to do with Prisma before we start to bind it to our Express project is to generate some initial data. In order to generate some data we need an additional package PrismaClient.

npm i @prisma/client

Now let's use it.

// prisma/seed.ts
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function run() {
  const user = await prisma.user.create({
    data: {
      email: "foo@gmail.com",
      username: "foo",
    },
  });
  console.log(user);
}

run()
  .catch((e) => {
    console.log(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

Here we call a run function that we created which generates for us a user in the database. Now we can add any creations in this run function. Most importantly we get a full autocomplete for prisma.user.create because Prisma knows exactly what entities we have.

The last thing that we are missing for seeding data is adding the prisma seed configuration to package.json.

"prisma": {
  "seed": "ts-node prisma/seed.ts"
}

Here we provided a command to execute our seed file.

npx prisma db seed

Seed

As you can see here our command was executed and 1 user was created in our database.

Generating Prisma client

Before we jump in using Prisma together with Express I need to show you super important command.

npx prisma generate

generate

This command loads the schema from prisma/schema.prisma and it generates Prisma Client. It generates it in ./node_modules/@prisma/client. In order to understand what is Prisma Client we must understand how it works. Inside seed.ts we already used prisma.user.create. And it looks like magic because somehow Prisma can understand entities that we created inside schema.prisma when we type Typescript.

This generate command reads our schema.prisma and creates all Typescript types to cover our entities.

node_modules

There is no magic Prisma just generates such types based on the entities that we wrote inside. But the most common problem for people that start using Prisma is that they forget to run this generate command and their schema file is out of sync and they are getting some magic Typescript errors.

Every single time when you update your Prisma schema you must run generate command.

Implementation

With that being said let's implement our Express project. We want to create an API which manages our users. So we need an API to get a list of users, create a user, update a user, delete a user and get a user by id.

// src/index.ts
import express from "express";
import { PrismaClient } from "@prisma/client";

const app = express();
const prisma = new PrismaClient();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.get("/users", async (req, res) => {
  const result = await prisma.user.findMany();
  res.json(result);
});

app.listen(3000, () => {
  console.log("API started");
});

Here is our basic Express project and as you can see we created a prisma by calling new PrismaClient(). We also added a /users route which calls prisma.user.findMany() to return a list of all our users.

get request

As you can see now we can get a list of users with the small amount of code that we wrote.

Now we want to be able to create a user.

app.post("/users", async (req, res) => {
  try {
    const result = await prisma.user.create({
      data: req.body,
    });
    res.json(result);
  } catch (err) {
    res.json({ error: "Email or username are not unique" });
  }
});

Here we use prisma.user.create to do that. We also has unique validation for the email so we must handle any error that we can get with Prisma with try and catch.

Create user

Here we can now create a user in our database.

app.get("/users/:id", async (req, res) => {
  const result = await prisma.user.findUnique({
    where: {
      id: Number(req.params.id),
    },
  });
  res.json(result);
});

Here we implemented finding a user by ID. In order to do that we use prisma.user.findUnique.

app.put("/users/:id", async (req, res) => {
  const result = await prisma.user.update({
    where: {
      id: Number(req.params.id),
    },
    data: req.body,
  });
  res.json(result);
});

In order to update a user to can use prisma.user.update where we write a condition what to update and the data.

app.delete("/users/:id", async (req, res) => {
  const result = await prisma.user.delete({
    where: {
      id: Number(req.params.id),
    },
  });
  res.json(result);
});

And the last one is deletion of our user by ID which is similar to updating.

As you can see Prisma simplifies a lot working with the database.

And actually if you want to improve your Javascript knowledge and prepare for the interview I highly recommend you to check my course Javascript Interview Questions.

📚 Source code of what we've done