6 Relational Mapping

Relational Mapping

Check this guide (opens in a new tab) to learn more about Prisma and ORM.

Database + Schema

Install the Prisma extension (opens in a new tab) for Visual Studio Code from the marketplace.

  1. Create a Next.js application using npx create-next-app@latest.1

  2. Install the Prisma package using npm install --save-dev prisma.

  3. Initialize Prisma with SQLite (opens in a new tab) as data source provider using prisma init --datasource-provider sqlite --output ./client.2

  4. The environment variable DATABASE_URL in .env can be updated with the desired location of the database file, for example, DATABASE_URL="file:data/dev.db".

  5. Update the schema, prisma/schema.prisma, with data models for a one-to-many relationship between users and their posts:

    • Each User has an identifier, a unique email, a name, and a list of posts.
    • Each Post has an identifier, a title, a text content, a published status, and a corresponding author.
    ER diagram for User and Post modelsUserid: Stringemail: Stringname: StringPostid: Stringtitle: Stringcontent: Stringpublished: Booleanauthor: String
    prisma/schema.prisma
    model User {
      id    String  @id @default(cuid(2))
      email String  @unique
      name  String?
      posts Post[]
    }
     
    model Post {
      id         String  @id @default(cuid(2))
      title      String
      content    String?
      published  Boolean @default(false)
      postAuthor User    @relation(fields: [author], references: [id])
      author     String
    }
  6. Create the supporting SQLite database using prisma migrate dev --name init. This will generate and execute a SQL migration3 against the database4 to define its tables and relationships.

  7. Use Prisma Studio, prisma studio, to view the database tables and edit/filter their records, if needed.

Client + Repository

  1. Create a repository module, repos/users.js, to manage access to the data.

  2. Install5 the client library using npm install @prisma/client and use it to access the data in the repository:

    repos/users.js
    import { PrismaClient } from "@/prisma/client";
    const prisma = new PrismaClient();

    The client is custom-tailored to the data model and must be regenerated using prisma generate whenever the schema is updated6.

  3. Create a disconnect() method that closes a client connection, which will be used after having executed a query:

    repos/users.js
    async function disconnect() {
      try {
        await prisma.$disconnect();
      } catch (e) {
        console.error(e);
        await prisma.$disconnect();
        process.exit(1);
      }
    }
  4. Create a read() method that returns all users along with their posts. It should call disconnect() after executing the queries and before returning the results. Use prisma.user.findMany() and include the posts:

    repos/users.js
    export async function read() {
      const users = await prisma.user.findMany({
        include: {
          posts: true,
        },
      });
     
      await disconnect();
      return users;
    }

    The generated SQL queries can be logged by setting the log option in the PrismaClient constructor: new PrismaClient({ log: ["query"] }).

  5. In a long-running application, prisma.$disconnect() should not be explicitly called. One approach is to avoid calling disconnect() and instead create a client module, repos/prisma.js, then import it in repos/users.js to cache the PrismaClient instance and reuse it across the application:

    repos/prisma.js
    import { PrismaClient } from "@/prisma/client";
    const prisma = new PrismaClient();
    export default prisma;
    repos/users.js
    import prisma from "@/repos/prisma";

RESTful API

  1. Import and use the repository in api/users/route.js to implement a GET method that returns all users along with their posts using read():

    app/api/users/route.js
    import * as users from "@/repos/users.js";
     
    export async function GET(request) {
      return Response.json(await users.read());
    }
  2. Test the route using Postman (opens in a new tab).

Seeding

  1. Create a prisma/seed.mjs script that uses the Faker (opens in a new tab) package7 to procedurally generate sample data and populates the database using prisma.user.create():

    prisma/seed.mjs
    import { PrismaClient } from "./client/index.js";
    import { faker } from "@faker-js/faker";
    const prisma = new PrismaClient();
     
    const seed = async () => {
      Array.from({ length: Math.floor(Math.random() * 60) }).forEach(
        async () =>
          await prisma.user.create({
            data: {
              email: faker.internet.email(),
              name: faker.person.fullName(),
              posts: {
                create: Array.from({
                  length: Math.floor(Math.random() * 12),
                }).map(() => ({
                  title: faker.commerce.productName(),
                  content: faker.lorem.text(),
                  published: Math.random() > 0.5,
                })),
              },
            },
          }),
      );
    };
     
    try {
      await seed();
      await prisma.$disconnect();
    } catch (e) {
      console.error(e);
      await prisma.$disconnect();
      process.exit(1);
    }
  2. Add the following lines at the top level of package.json to automatically seed the database in prisma migrate dev and prisma migrate reset. The script can also be manually invoked using prisma db seed:

    package.json
    {
      •••
      "prisma": {
        "seed": "node prisma/seed.mjs"
      }
    }

Footnotes

  1. This guide is based on the Prisma Get Started / Quickstart (opens in a new tab) guide.

  2. Use npx prisma [command] for all Prisma commands.

  3. prisma db push can be used instead if migrations are not needed.

  4. prisma db pull can be used instead with a pre-existing database.

  5. prisma migrate already performs this step.

  6. Install Prisma DBML Generator (opens in a new tab) to automatically generate DBML (opens in a new tab) diagrams.

  7. Install the Faker package using npm install --save-dev @faker-js/faker.