Next.js 13 App directory + Prisma
Tags: nextjs, prisma, webdev, typescript
Published: 2023-02-05 20:06:13

## Introduction

Next.js 13 introduced the app directory with new features and conventions. This feature is still in beta, but we already have a nice overview of what the future will be like.

See [https://beta.nextjs.org/docs/app-directory-roadmap](https://beta.nextjs.org/docs/app-directory-roadmap) for the roadmap.

## Installation

To automatically create a new Next.js project using the `app` directory:

```bash
npx create-next-app@latest --experimental-app
```

After executing this command, you will see the new project architecture :

```
app/
├─ globals.css
├─ head.tsx
├─ layout.tsx
├─ page.module.css
├─ page.tsx
```

Where is the `index.tsx` file ?

To create a route for `/profile`, you may be used to create a `profile.tsx` file or `/profile/index.tsx`. With the app directory, you now must create a new folder with a `page.tsx` file.

For example, the route `/profile` would be created as following :


```
app/
├─ globals.css
├─ head.tsx
├─ layout.tsx
├─ page.module.css
├─ page.tsx
├─ profile/
│  ├─ page.tsx
```

## Introducing Prisma

> Prisma is a next-generation Node.js and TypeScript ORM for PostgreSQL, MySQL, SQL Server, SQLite, MongoDB, and CockroachDB. It provides type-safety, automated migrations, and an intuitive data model.

Before, we may use tRPC or API routes to call Prisma. But now, with the app directory and server components, Prisma can be called directly inside the component.

### Adding Prisma to the project

```bash
npm install prisma --save-dev
npx prisma init --datasource-provider sqlite
```

This creates a new prisma directory with your Prisma schema file and configures SQLite as your database. You're now ready to model your data and create your database with some tables.

The Prisma schema provides an intuitive way to model data. Add the following models to your `schema.prisma` file:

```prisma
model Todo {
  id        Int      @id @default(autoincrement())
  title     String
  completed Boolean  @default(false)
  createdAt DateTime @default(now())
}
```

We now need to migrate the database. Run the following :

```bash
npx prisma migrate dev --name init
```

To generate the typescript types, run :
```bash
npx prisma generate
```

### Seed the database

Create a `prisma/seed.ts` file and add the following :
```ts
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function main() {
  await prisma.todo.create({
    data: {
      title: "Learn Next.js",
    },
  });
  await prisma.todo.create({
    data: {
      title: "Learn Prisma",
    },
  });
  await prisma.todo.create({
    data: {
      title: "Learn GraphQL",
    },
  });
}

main()
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });
```

And in you `package.json` file :

```json
{
  "name": "nextjs13-app-prisma",
  "version": "0.1.0",
  "private": true,
...
  "prisma": {
    "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
  },
...
}
```

Now, when you run the `npx prisma seed` command, it will seed your database.

## Link Prisma and Next

Create a `utils/prisma.ts` file :

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

const prisma = new PrismaClient()

export default prisma;
```

This will ensure that only one Prisma instance is running.

By default, all components are Server Components. Therefore, you can use async code in every one of them.

For example, to fetch all the to-dos in the home page, use :

```ts
import prisma from "@/utils/prisma";

export default async function Home() {
  const todos = await prisma.todo.findMany();
  return (
    <main>
      <h1 className="font-bold">Todos</h1>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </main>
  );
}
```

(I added Tailwind for styling, it is of course not required)

Go to `localhost:3000` and you will see all your to-dos :

![Index page with to-dos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e88zhsxb7kryhca0e69l.png)

## Data mutation

To mutate data, it is a bit more complicated. Because we need Client Component for user interactivity and because we can not have server code inside Client Component, we need to set up an API route.

Create the `pages/api/todo.ts` file :

```ts
import { NextApiRequest, NextApiResponse } from "next";
import prisma from "@/utils/prisma";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === "PUT") {
    await prisma.todo.update({
      where: {
        id: parseInt(req.body.id),
      },
      data: {
        completed: req.body.completed,
      },
    });
    res.status(200).json({ message: "Updated" });
  } else {
    // 404
    res.status(404).json({ message: "Not found" });
  }
}
```

In this API endpoint, we get the to-do id and state in the body and update the corresponding to-do.

We will now create a Todo Client Component.

In a `app/todo.tsx` file (remember that it will not create a `/todo` route) :

```tsx
"use client";

import { Todo } from "@prisma/client";
import { useRouter } from "next/navigation";

export default function TodoComponent({ todo }: { todo: Todo }) {
  const router = useRouter();
  const update = async (todo: Todo) => {
    await fetch(`/api/todo`, {
      method: "PUT",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        completed: !todo.completed,
        id: todo.id,
      }),
    });
    router.refresh();
  };

  return (
    <li key={todo.id} className="space-x-4">
      <input
        onChange={() => update(todo)}
        type="checkbox"
        checked={todo.completed}
      />
      {todo.title}
    </li>
  );
}
```

The UI is updated using the `router.refresh()` method.
Note the `"use client"` at the top, it is required to let Next know that it is a Client Component.

We can then update the Home page :

```tsx
import prisma from "@/utils/prisma";
import Todo from "@/app/todo";

export default async function Home() {
  const todos = await prisma.todo.findMany();
  return (
    <main>
      <h1 className="font-bold">Todos</h1>
      <ul>
        {todos.map((todo) => (
          <Todo todo={todo}></Todo>
        ))}
      </ul>
    </main>
  );
}
```

Enjoy your simple but working to-do in Next.js 13 using Prisma.

![Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsd7ini873akerewehaw.gif)

Find the code for the article on [GitHub](https://github.com/Pixselve/nextjs13-app-prisma).