Веб-разработка с Prisma и Next.js
В этом посте мы узнаем, как использовать Prisma, ORM для JavaScript и TypeScript, в приложении Next.js.
Что такое Prisma?
Prisma — это инструмент ORM с открытым исходным кодом для Node.js и TypeScript, который упрощает подключение, выполнение запросов, миграцию и моделирование данных в базах данных SQL.
ORM — это объектно-реляционное сопоставление, это метод, с помощью которого мы можем запрашивать данные, подключаться к базам данных и манипулировать ими, используя объектно-ориентированную парадигму. ORM могут быть написаны на любом языке, на каком бы языке они ни были написаны, они инкапсулируют код, необходимый для управления базой данных. Это исключает использование SQL, поскольку об этом заботится библиотека ORM. ORM представляет каждую таблицу в схеме данных как один класс. Например, таблица Food превратится в класс в библиотеке ORM, и мы сможем читать все продукты, удалять продукты и т. д., используя этот класс.
Чтобы запросить все продукты в базе данных вручную, мы напишем код:
var mysql = require("mysql");
var connection = mysql.createConnection({
host: "localhost",
user: "me",
password: "secret",
database: "my_db",
});
connection.connect();
connection.query("SELECT * FROM food", function (error, results, fields) {
if (error) throw error;
console.log("Results: ", results);
});
connection.end();
Столько кода. Но с библиотекой ORM это будет так:
const allFoods = Food.getAll("foods)
Просто и аккуратно. С Prisma все проще:
async function main() {
const allFoods = await prisma.food.findMany();
console.log(allFoods);
}
Использовать Prisma очень просто. Команда Prisma предоставляет нам инструмент CLI, который позволяет легко создавать клиентский код Prisma. Чтобы начать работу с Prisma, мы определяем схему нашей базы данных в файле Schema.prisma:
model Food {
id Int @default(autoincrement()) @id
name String?
}
Prisma выполнит их миграцию в нашу базу данных. Теперь мы будем использовать клиентскую библиотеку Prisma для запроса базы данных.
// Get all foods in the "food" db
await prisma.food.findMany();
// Create new food
await prisma.food.create({
data: {
name: "Jollof Rice",
},
});
// Update the food db
await prisma.food.update({
where: { id: 1 },
data: { name: "Rice and Steww" },
});
Методы findMany, create и update выполняют SQL-запросы к нашей базе данных. Но, как мы видим, нам не нужно писать команды и запросы SQL, Prisma позаботится об этом за нас. У нас остался более чистый код, который легче поддерживать. Механическую часть автоматически берет на себя Prisma.
Prisma и Next.js
Prisma хорошо интегрируется с Next.js. Next.js сочетает в себе как внешний, так и внутренний код. Это означает, что мы можем создавать компоненты пользовательского интерфейса в Next.js, а также создавать наши маршруты API в том же приложении Next.js, поэтому Next.js содержит клиент и сервер.
Поскольку Prisma подключается к базе данных, мы можем идеально вызывать ее в нашем приложении Next.js для получения данных из серверной части. В Next.js есть много способов получения данных. В Next.js есть три уникальные функции, которые мы можем использовать для получения данных для предварительного рендеринга:
getStaticProps : получение данных во время сборки. Мы будем использовать клиент Prisma для выполнения запросов к нашей БД.
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export async function getStaticProps() {
// Get all foods in the "food" db
const allfoods = await prisma.food.findMany();
return {
props: allFoods,
};
}
Next.js выполнит запрос во время сборки для получения данных, затем мы вернем результаты в объект реквизита, это заставит Next.js передать результаты реквизитам соответствующего компонента.
getServerSideProps (рендеринг на стороне сервера) : запускается, когда страница предварительно визуализируется при каждом запросе. Мы также можем вызвать здесь клиентские методы Prisma для получения данных, которые мы хотим передать компонентам Next.js.
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export async function getServerSideProps() {
// Get all foods in the "food" db
const allfoods = await prisma.food.findMany();
return {
props: allFoods,
};
}
Маршруты API : маршруты API в Next.js хранятся в папке pages/api. Каждый файл и папка сопоставляются с конечной точкой API. Они обслуживаются по тому же URL-адресу, что и код внешнего интерфейса, localhost:3000.
Так как localhost:3000/foods отображает страницу с едой, так и localhost:3000/api/getAllFoods — это конечная точка API, которая возвращает списки рецептов еды из приложения Next.js.
Отсюда мы можем совершать звонки в нашу базу данных через Prisma. Например, конечная точка localhost:3000/api/getAllFoods может запросить базу данных, чтобы получить все рецепты блюд, и отправить их в качестве ответа:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default async (req, res) => {
const data = req.body;
try {
const result = await prisma.foods.findMany();
res.status(200).json(result);
} catch (err) {
console.log(err);
res.status(403).json({ err: "Error occured." });
}
};
Посмотрите, что мы используем Prisma из Node.js; маршруты API, getServerSideProps и getStaticProps. Все они запускаются на сервере Next.js, мы не можем использовать Prisma из браузера:
...
function Foods(props) {
//...
useEffect(async () => {
await prisma.food.findMany();
});
//...
}
...
это приведет к ошибке:
Error: PrismaClient is unable to be run in the browser.
In case this error is unexpected for you, please report it in https://github.com/prisma/prisma/issues
Чтобы полностью продемонстрировать, как использовать Prisma и Next.js, мы создадим приложение Food в Next.js.
Приложение Еда сможет:
- перечислить все продукты
- получить конкретный
- Показать еду; его состав, описание и цена.
- Удалить еду
- Редактировать рецепт блюда.
Все эти действия будет выполнять Prisma из приложения Next.js. Продукты будут храниться в базе данных Postgres, и мы будем использовать клиент Prisma, чтобы получить все продукты и изменить их, как указано выше. Наше окончательное приложение будет выглядеть так:
Отображает рецепты блюд
Посмотреть рецепт блюда
Добавить рецепт блюда
В нашем приложении будет две страницы: одна для отображения всех рецептов блюд, а другая для отображения конкретного рецепта еды. Таким образом, наше приложение Next.js будет иметь два компонента для двух представлений/маршрутов/страниц. Маршруты страниц будут:
- /foods: отображает все рецепты блюд в нашем приложении.
- /food: Отобразить конкретный рецепт блюда.
У нас будут презентационные компоненты, которые будут отображать небольшие детали каждого рецепта на странице списка продуктов, а также компонент заголовка для отображения заголовка. У нас будет два модальных окна/диалога, в которых мы сможем добавлять новые рецепты блюд и редактировать существующие рецепты блюд.
У нас будут маршруты API, которые будут предоставлять три конечные точки: одну для добавления нового рецепта еды в базу данных, другую для редактирования определенного рецепта еды и последнюю для удаления рецепта еды из базы данных. Давайте приступим.
Настройте Postgres на вашем компьютере
В качестве базы данных мы будем использовать Postgres. Prisma подключается к любой базе данных SQL: Postgres, MySQL, SQL Server, SQLite. Он также имеет подключения к MongoDB. Если на вашем компьютере не установлен PostgresSQL, перейдите к загрузкам PostgresSQL и загрузите двоичные файлы для вашего компьютера.
После установки запустите сервер Postgres. Обязательно запомните порт Postgres, имя пользователя и пароль, поскольку мы будем использовать их при подключении клиента Prisma к Postgres.
Создание проекта Next.js
Теперь мы создадим приложение Next.js, для этого запустите команду:
npx create-next-app prisma-next
# OR
yarn create next-app prisma-next
Перейдите внутрь папки: cd prisma-next. Мы установим следующие зависимости:
- axios: HTTP-библиотека для выполнения HTTP-запросов.
- @prisma/client: клиент Prisma для JavaScript. Он работает только на Node.js.
Запустите следующую команду:
yarn add axios @prisma-client
Все готово, далее мы инициализируем среду Prisma в нашем проекте.
Prisma инициализация
Запустите следующую команду:
npx prisma init
Эта команда создаст папку prisma внутри проекта prisma-next и файл .env. Папка prisma будет содержать файл Schema.prisma, здесь мы объявляем наши модели базы данных Prisma. Далее мы настраиваем соединения Prisma с нашей базой данных Postgres.
Настройте подключение Prisma к базе данных
Откройте .env, мы увидим, что у него есть DATABASE_URL:
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
Это URL-соединение с сервером базы данных Postgres.
- johndoe — имя пользователя базы данных.
- randompassword — пароль пользователя базы данных.
- localhost — хост базы данных.
- 5432 — номер порта, по умолчанию всегда 5432.
- mydb — это имя базы данных, к которой вы хотите подключиться. Создайте это на своем сервере Postgres.
Теперь измените их на свои собственные данные Postgres:
DATABASE_URL="postgresql://postgres:0000@localhost:5432/food"
Определите нашу модель
Теперь мы определим нашу модель. Так как наше приложение будет посвящено теме Рецепты еды. Наша модель питания будет такой:
model Food {
id Int @default(autoincrement()) @id
name String?
price String?
ingredients String?
active String?
description String?
imageUrl String?
}
Идентификатор устанавливается базой данных и автоматически увеличивается. Имя будет названием рецепта блюда. Цена будет равна цене рецепта еды. Ингредиенты будут содержать ингредиенты, используемые для приготовления пищи. Активный указывает, доступна ли эта еда в данный момент. В описании описан рецепт блюда. imageUrl — это URL-адрес изображения рецепта еды. Модель Food будет сопоставлена с таблицей в нашей базе данных.
Запуск миграции
Далее запускаем миграцию. Запуск миграции создаст файл миграции SQL для текущей схемы и запустит миграцию в базе данных. Миграции выполняются всякий раз, когда мы обновляем схему.
Миграции — это просто команды SQL, которые генерируются на основе того, что было выполнено в схеме. Если мы создадим новую модель в схеме, миграция создаст команду SQL для создания таблицы, например:
-- CreateTable
CREATE TABLE "newTable" (
"id" SERIAL NOT NULL,
"name" TEXT,
PRIMARY KEY ("id")
);
Затем эта команда SQL будет выполнена для создания таблицы в базе данных. Это краткая информация о выполнении миграций. Поэтому всякий раз, когда вы слышите о миграции, имейте в виду, что именно это и происходит. Хорошо, вернемся к нашему проекту. Теперь мы запускаем приведенную ниже команду для запуска миграции в нашей базе данных Postgres:
npx prisma migrate dev --name init
Эта команда сгенерирует файл миграции и запустит его для вашей базы данных. Подаргумент --name задает имя миграции. Значение init будет именем созданной папки миграции.
Вывод: {NUMBER_GENERATED_BY_PRISMA}_init. Таким образом, это создаст папку/файл внутри папки prisma/migrations. Новая папка с SQL-файломmigration.sql будет создаваться для каждого запуска миграции. На моей машине команда сгенерировала это:
prisma/
migrations/
20210521082328_init/
migration.sql
migration.sql содержит команду SQL для создания таблицы Food:
-- CreateTable
CREATE TABLE "Food" (
"id" SERIAL NOT NULL,
"name" TEXT,
"price" TEXT,
"ingredients" TEXT,
"active" TEXT,
"description" TEXT,
PRIMARY KEY ("id")
);
Это потому, что мы добавили модель в схему. Итак, Prisma сгенерировала команду из схемы и запустила ее. Хорошо, давайте создадим наши компоненты.
Сборка компонентов
Как мы уже говорили, в нашем приложении будет две страницы: еда и еда. Давайте создадим их:
mkdir pages/foods
touch pages/foods/index.js
touch pages/foods/Foods.module.css
mkdir pages/food
touch pages/food/[id].js
touch pages/food/Food.module.css
food — это динамический маршрут, поэтому мы использовали [id].js. Синтаксис квадратных скобок позволяет сообщить Next.js, что маршрут является динамическим. Маршрут может сопоставляться с чем угодно, например, с едой/1, едой/2, едой/1987 и т. д. Мы создадим презентационные компоненты: заголовок и FoodCard:
mkdir components
mkdir components/header
touch components/header/index.js
touch components/header/Header.module.css
mkdir components/foodcard
touch components/foodcard/index.js
touch components/foodcard/FoodCard.module.css
Создаём модальные компоненты: editfood и addfood:
mkdir components/editfood
touch components/editfood/index.js
touch components/editfood/EditFood.module.css
mkdir components/addfood
touch components/addfood/index.js
touch components/addfood/AddFood.module.css
*.module.css — это способ CSS-модулей для стилизации каждого компонента отдельно. Теперь мы детализируем компоненты:
FoodCard
Откройте components/foodcard/index.js и вставьте приведенный ниже код:
import styles from "./FoodCard.module.css";
import Link from "next/link";
export default function FoodCard({ food }) {
return (
<Link href={`/food/${food.id}`}>
<div className={styles.foodCard}>
<div
alt={`Food Image of: ${food?.name}`}
aria-label={`Food Image of: ${food?.name}`}
className={styles.foodCardImg}
style={{ backgroundImage: `url(${food.imageUrl})` }}
></div>
<div className={styles.foodCardFooter}>
<div className={styles.foodCardName}>
<h3>{food.name}</h3>
</div>
<div className={styles.foodCardPrice}>
<span>Price(💵)</span>
<span>{food.price}</span>
</div>
<div className={styles.foodCardActive}>
<span>Active:</span>
<span>{food.active}</span>
</div>
</div>
</div>
</Link>
);
}
Этот компонент FoodCard отображает подробную информацию о рецепте еды, а именно название, изображение, цену и доступность еды. Он принимает рецепт еды из своего реквизита в файле props.food.
Заголовок
Откройте компоненты/header/index.js и вставьте приведенный ниже код:
import styles from "./Header.module.css";
export default function Headers() {
return (
<header className={styles.header}>
<div className={styles.headerName}>🥗🥘🍱🍛</div>
</header>
);
}
Этот компонент отображает заголовок.
Продукты питания
Откройте страницы/foods/index.js и вставьте приведенный ниже код:
import styles from "./Foods.module.css";
import FoodCard from "./../../components/foodcard";
import { PrismaClient } from "@prisma/client";
import AddFood from "../../components/addfood";
import { useState } from "react";
const prisma = new PrismaClient();
function Foods(props) {
const [showAddFoodModal, setShowAddFoodModal] = useState(false);
const foods = props.foods;
return (
<div className={styles.foodsCnt}>
<div className={styles.foodsBreadcrumb}>
<div>
<h2>Recipes 🥗🥘🍱🍛</h2>
</div>
<div>
<button
className="btn"
style={{
paddingLeft: "15px",
paddingRight: "15px",
fontWeight: "500",
}}
onClick={() => setShowAddFoodModal((pV) => !pV)}
>
Add Food
</button>
</div>
</div>
<div className={styles.foods}>
{foods?.map((food, i) => (
<FoodCard food={food} key={i} />
))}
</div>
{showAddFoodModal ? (
<AddFood closeModal={() => setShowAddFoodModal(false)} />
) : null}
</div>
);
}
export async function getServerSideProps() {
const allFoods = await prisma.food.findMany();
return {
props: {
foods: allFoods,
},
};
}
export default Foods;
Этот компонент извлекает рецепты блюд и отображает их в списке. Посмотрите, что он инициализирует клиент Prism:
const prisma = new PrismaClient();
А продукты получаются методом getServerSideProps.
export async function getServerSideProps() {
const allFoods = await prisma.food.findMany();
return {
props: {
foods: allFoods,
},
};
}
Мы вызываем метод prisma.food.findMany(), чтобы получить все продукты на столе. Убедитесь, что модели в prisma.schema доступны в объекте prisma. У нас есть модель еды, поэтому в объекте призмы есть еда. Теперь каждая модель, доступная в объекте prisma, содержит методы для получения всех данных модели, изменения данных модели и удаления данных модели: findMany, update, upsert, delete, find и т. д.
Поэтому мы устанавливаем ключ продуктов питания в реквизитах, чтобы хранить результаты рецептов еды в allFoods, чтобы таким образом Foods мог получить доступ к рецептам еды, возвращенным, выполнив этот props.foods. Заглянув внутрь компонента Foods, мы видим состояние showAddFoodModal, которое мы используем для переключения модального окна AddFood. Затем рецепты блюд извлекаются из реквизита и сохраняются в переменной food. Затем мы отображаем рецепты блюд в массиве продуктов, каждый продукт в массиве отображается с помощью FoodCard. Каждая еда в массиве передается ему через свойство food.
Еда
Откройте страницы/food/index.js и вставьте приведенный ниже код:
import styles from "./Food.module.css";
import { PrismaClient } from "@prisma/client";
import { useState } from "react";
import EditFood from "../../components/editfood";
import axios from "axios";
import { useRouter } from "next/router";
const prisma = new PrismaClient();
export default function Food(props) {
const [showEditFoodModal, setShowEditFoodModal] = useState(false);
const router = useRouter();
const { food } = props;
async function deleteFood() {
if (window.confirm("Do you want to delete this food?")) {
// ...
await axios.post("/api/deleteFood", { id: parseInt(food?.id) });
router.push("/foods");
}
}
return (
<div className={styles.foodContainer}>
<div className={styles.food}>
<div
alt={`Food Image of: ${food?.name}`}
aria-label={`Food Image of: ${food?.name}`}
className={styles.foodImage}
style={{ backgroundImage: `url(${food?.imageUrl})` }}
></div>
<div className={styles.foodDetails}>
<div className={styles.foodName}>
<h1>{food?.name}</h1>
</div>
<div style={{ padding: "5px 0" }}>
<span>
<button
onClick={() => setShowEditFoodModal((pV) => !pV)}
style={{ marginLeft: "0" }}
className="btn"
>
Edit
</button>
<button onClick={deleteFood} className="btn btn-danger">
Delete
</button>
</span>
</div>
<div style={{ padding: "5px 0" }}>
<span> Price(💵): {food?.price}</span>
</div>
<div className={styles.foodDescIngreCnt}>
<h2>Ingredients</h2>
<div className={styles.foodSynopsis}>{food?.ingredients}</div>
</div>
<div className={styles.foodDescIngreCnt}>
<h2>Description</h2>
<div className={styles.foodSynopsis}>{food?.description}</div>
</div>
</div>
</div>
{showEditFoodModal ? (
<EditFood food={food} closeModal={() => setShowEditFoodModal(false)} />
) : null}
</div>
);
}
export async function getServerSideProps(context) {
const { id } = context.params;
const food = await prisma.food.findUnique({ where: { id: parseInt(id) } });
return {
props: {
food,
},
};
}
Эта страница получит идентификатор рецепта еды, будет использовать этот идентификатор для получения рецепта еды и отобразит все его сведения. См. метод getServerSideProps:
export async function getServerSideProps(context) {
const { id } = context.params;
const food = await prisma.food.findUnique({ where: { id: parseInt(id) } });
return {
props: {
food,
},
};
}
Мы получили значение идентификатора маршрута из context.params. Теперь мы вызвали метод prisma.food.findUnique, чтобы получить рецепт еды, идентификатор которого равен значению идентификатора, полученного из context.params. Результат передается объекту реквизита в ключе питания.
Компонент Food получает результат еды из своего аргумента props и использует его для рендеринга всего содержимого рецепта еды. Есть кнопки «Изменить» и «Удалить». Кнопка «Удалить» при нажатии удаляет еду из базы данных. Он вызывает API/deleteFood в нашем приложении, конечная точка выполняет работу и возвращает ответ, и мы переходим к списку продуктов, поскольку рецепт блюда, который мы просматриваем, больше не существует. Edit вызывает модальное окно EditFood, позволяющее нам редактировать текущую еду, которую мы просматриваем. Это делается с помощью состояния showEditFoodModal, оно включает или выключает модальное окно.
Редактировать Еда
Откройте components/editfood/index.js и вставьте приведенный ниже код:
import { useState, useRef } from "react";
import axios from "axios";
export default function EditFood({ closeModal, food }) {
const formRef = useRef();
const [disable, setDisable] = useState(false);
async function editFood() {
setDisable(true);
const {
editFoodName,
editFoodPrice,
editFoodImageUrl,
editFoodActive,
editFoodDescription,
editFoodIngredients,
} = formRef.current;
const name = editFoodName.value;
const price = editFoodPrice.value;
const imageUrl = editFoodImageUrl.value;
const active = editFoodActive.value;
const description = editFoodDescription.value;
const ingredients = editFoodIngredients.value;
await axios.put("/api/editFood", {
id: parseInt(food?.id),
name,
price,
imageUrl,
active,
description,
ingredients,
});
setDisable(false);
window.location.reload();
}
return (
<div className="modal">
<div className="modal-backdrop" onClick={() => closeModal()}></div>
<div className="modal-content">
<div className="modal-header">
<h3>Edit Food</h3>
<span
style={{ padding: "10px", cursor: "pointer" }}
onClick={() => closeModal()}
>
X
</span>
</div>
<div className="modal-body content">
<form ref={formRef}>
<div style={{ display: "flex", margin: "2px 2px 0 0" }}>
<div
style={{ flex: "1 1 100%", margin: "0 0 2px 5px" }}
className="inputField"
>
<div className="label">
<label>Name</label>
</div>
<div>
<input
defaultValue={food?.name}
name="editFoodName"
type="text"
/>
</div>
</div>
<div
style={{ flex: "1 1 50%", margin: "0 0 2px 5px" }}
className="inputField"
>
<div className="label">
<label>Price($)</label>
</div>
<div>
<input
defaultValue={food?.price}
name="editFoodPrice"
type="text"
/>
</div>
</div>
<div
style={{ flex: "1 1 50%", margin: "0 0 2px 5px" }}
className="inputField"
>
<div className="label">
<label>Active</label>
</div>
<div>
<input
defaultValue={food?.active}
name="editFoodActive"
type="text"
/>
</div>
</div>
</div>
<div className="inputField">
<div className="label">
<label>ImageUrl</label>
</div>
<div>
<input
defaultValue={food?.imageUrl}
name="editFoodImageUrl"
type="text"
/>
</div>
</div>
<div className="inputField">
<div className="label">
<label>Ingredients</label>
</div>
<div>
<textarea
defaultValue={food?.ingredients}
style={{ width: "100%", height: "100px" }}
name="editFoodIngredients"
type="text"
></textarea>
</div>
</div>
<div className="inputField">
<div className="label">
<label>Description</label>
</div>
<div>
<textarea
defaultValue={food?.description}
style={{ width: "100%", height: "100px" }}
name="editFoodDescription"
type="text"
></textarea>
</div>
</div>
</form>
</div>
<div className="modal-footer">
<button onClick={() => closeModal()}>Cancel</button>
<button disabled={disable} className="btn" onClick={() => editFood()}>
Save
</button>
</div>
</div>
</div>
);
}
Это модальный компонент, с помощью которого мы можем редактировать переданный ему рецепт блюда. Еда для редактирования передается через аргумент props в ключе еды. Кроме того, также передается функция closeModal, она вызывается из компонента для переключения видимости модального окна.
Компонент имеет поля ввода, которые отображают значения информации о еде, мы можем редактировать их там и нажать кнопку «Сохранить». При этом вызывается метод editFood, который извлекает значения из полей ввода и вызывает конечную точку API /api/editFood с данными. Эта конечная точка API отвечает за обновление информации о еде в базе данных.
Добавить еду
Откройте файл components/addfood/index.js и вставьте приведенный ниже код:
import { useRef, useState } from "react";
import axios from "axios";
export default function AddFood({ closeModal }) {
const [disable, setDisable] = useState(false);
const formRef = useRef();
async function addNewFood(params) {
setDisable(true);
const {
addFoodName,
addFoodPrice,
addFoodImageUrl,
addFoodActive,
addFoodDescription,
addFoodIngredients,
} = formRef.current;
const name = addFoodName.value;
const price = addFoodPrice.value;
const imageUrl = addFoodImageUrl.value;
const active = addFoodActive.value;
const description = addFoodDescription.value;
const ingredients = addFoodIngredients.value;
await axios.post("/api/addFood", {
name,
price,
imageUrl,
active,
description,
ingredients,
});
setDisable(false);
window.location.reload();
}
return (
<div className="modal">
<div className="modal-backdrop" onClick={() => closeModal()}></div>
<div className="modal-content">
<div className="modal-header">
<h3>Add Food</h3>
<span
style={{ padding: "10px", cursor: "pointer" }}
onClick={() => closeModal()}
>
X
</span>
</div>
<div className="modal-body content">
<form ref={formRef}>
<div style={{ display: "flex", margin: "2px 2px 0 0" }}>
<div
style={{ flex: "1 1 100%", margin: "0 0 2px 5px" }}
className="inputField"
>
<div className="label">
<label>Name</label>
</div>
<div>
<input name="addFoodName" type="text" />
</div>
</div>
<div
style={{ flex: "1 1 50%", margin: "0 0 2px 5px" }}
className="inputField"
>
<div className="label">
<label>Price($)</label>
</div>
<div>
<input name="addFoodPrice" type="text" />
</div>
</div>
<div
style={{ flex: "1 1 50%", margin: "0 0 2px 5px" }}
className="inputField"
>
<div className="label">
<label>Active</label>
</div>
<div>
<input name="addFoodActive" type="text" />
</div>
</div>
</div>
<div className="inputField">
<div className="label">
<label>ImageUrl</label>
</div>
<div>
<input name="addFoodImageUrl" type="text" />
</div>
</div>
<div className="inputField">
<div className="label">
<label>Ingredients</label>
</div>
<div>
<textarea
style={{ width: "100%", height: "100px" }}
name="addFoodIngredients"
type="text"
></textarea>
</div>
</div>
<div className="inputField">
<div className="label">
<label>Description</label>
</div>
<div>
<textarea
style={{ width: "100%", height: "100px" }}
name="addFoodDescription"
type="text"
></textarea>
</div>
</div>
</form>
</div>
<div className="modal-footer">
<button style={{ marginLeft: "0" }} onClick={() => closeModal()}>
Cancel
</button>
<button
disabled={disable}
className="btn"
onClick={() => addNewFood()}
>
Add
</button>
</div>
</div>
</div>
);
}
Здесь мы создаем новый рецепт еды. В поля ввода мы добавляем информацию для нового рецепта еды. Кнопка «Добавить» при нажатии вызывает функцию addNewFood. Эта функция получит новые данные о еде из полей ввода и вызовет конечную точку API api/addFood с этой информацией.
Конечная точка отвечает за добавление нового рецепта еды в нашу базу данных. После вызова конечной точки API страница перезагружается, и мы видим только что созданный рецепт еды. Теперь мы закончили с нашими компонентами, давайте создадим конечные точки API.
Создание маршрутов API
Создайте следующие файлы:
touch pages/api/addFood.js
touch pages/api/editFood.js
touch pages/api/deleteFood.js
/api/addFood
API/addFood.js создаст новый рецепт еды в нашей базе данных. Вставьте в него приведенный ниже код:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default async (req, res) => {
const data = req.body;
try {
const result = await prisma.food.create({
data: {
...data,
},
});
res.status(200).json(result);
} catch (err) {
console.log(err);
res.status(403).json({ err: "Error occured while adding a new food." });
}
};
Данные рецепта еды извлекаются из аргумента req в объекте .body. Согласно документации Next.js :
req: экземпляр http.IncomingMessage плюс некоторые готовые промежуточные программы, которые вы можете увидеть здесь.
res: экземпляр http.ServerResponse плюс некоторые вспомогательные функции, которые вы можете увидеть здесь.
Итак, вернемся к нашему коду addFood.js. Мы вызываем метод prisma.food.create, передавая данные для создания нового рецепта еды в базе данных. Затем результат возвращается в виде ответа с кодом состояния 200.
Редактировать Еда
Откройте pages/api/editFood.js и вставьте приведенный ниже код:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default async (req, res) => {
const {
id,
name,
price,
imageUrl,
active,
description,
ingredients,
} = req.body;
try {
const updateFood = await prisma.food.update({
where: {
id: parseInt(id),
},
data: {
name,
price,
imageUrl,
active,
description,
ingredients,
},
});
res.status(200).json(updateFood);
} catch (error) {
res.status(403).json({ err: "Error occurred while updating a food item." });
}
};
Новая информация о рецепте еды извлекается из объекта req.body. Затем вызывается метод prisma.food.update для обновления рецепта еды, идентификатор которого равен идентификатору, извлеченному из req.body, новой информацией.
Удалить Еда
Откройте pages/api/deleteFood.js и вставьте приведенный ниже код:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default async (req, res) => {
const { id } = req.body;
try {
const deleteFood = await prisma.food.delete({
where: {
id,
},
});
res.status(200).json(deleteFood);
} catch (error) {
res.status(403).json({ err: "Error occured while deleting a food item." });
}
};
При этом извлекается идентификатор рецепта еды, который нужно удалить. Затем мы удалили рецепт еды, вызвав метод prisma.food.delete по идентификатору рецепта еды. Метод prisma.food.delete удаляет один рецепт блюда из записи Food в базе данных Postgres. Объектwhere используется, чтобы сообщить Prisma условие, при котором Prisma выберет запись из таблицы для удаления. Теперь давайте проверим наше приложение.
Тестирование
Запустите:
yarn dev
и откройте в браузере localhost:3000.
Добавляем новую еду:
Добавление новой еды (Сосати)
Сосати отображается в списке
Просмотр еды:
Просмотр рецепта блюда Сосати
Редактирование еды:
Редактирование Сосати
Отредактировано Сосати
Отредактировано отображение Сосати в списке блюд.
Удаление блюда:
Удаление Сосати
Сосати больше нет в списке
Заключение
Prisma поражает воображение. Комбинация с Next.js возможно станет лучшей комбинацией века. Использование базы данных SQL в Next.js прошло безупречно. Все механические части использования SQL были абстрагированы Prisma. Это идеально!🔥