برنامه نویسی

Typescript CRUD API: Next.js، Tailwind، tRPC، Prisma Postgres، Docker

در پایان این مقاله، نحوه ساخت یک برنامه FULL CRUD API، از جمله یک ظاهر ساده برای استفاده از آن، با استفاده از فناوری‌های زیر را خواهید فهمید:

  • Next.js
  • TypeScript
  • Tailwind CSS
  • tRPC
  • پریسما
  • Postgres
  • داکر

آنها فن آوری های زیادی هستند، اما ما مثال را تا حد امکان اساسی نگه می داریم تا قابل درک باشد. ما همچنین از Zod، یک کتابخانه اعتبار سنجی استفاده خواهیم کرد.

اگر نسخه ویدیویی را ترجیح می دهید:

https://www.youtube.com/watch?v=Gf9RkaHnsR8

همه کدها به صورت رایگان در GitHub در دسترس هستند (لینک در توضیحات ویدیو).

بیا شروع کنیم.


🏁 معرفی

در اینجا طرحی از معماری برنامه ای که می خواهیم ایجاد کنیم آمده است:

ما پنج نقطه پایانی برای عملیات اصلی CRUD ایجاد خواهیم کرد:

ايجاد كردن
همه اش را بخوان
یکی را بخوانید
به روز رسانی
حذف

ما برنامه را با استفاده از Create T3 App ایجاد می کنیم، و این برنامه را به یک نمونه Postgres که در یک ظرف داکر اجرا می شود متصل می کنیم.


👣 مراحل

ما با یک راهنمای گام به گام همراه می شویم تا بتوانید آن را دنبال کنید.

در اینجا مراحل انجام می شود:

  1. پیش نیازها
  2. ایجاد پروژه و نصب وابستگی
  3. پایگاه داده Postgres را با Docker اجرا کنید
  4. Prisma و شمای پایگاه داده را پیکربندی کنید
  5. رویه های tRPC را بنویسید
  6. کنترل کننده ها را در فایل index.tsx پیکربندی کنید
  7. برنامه ساده ظاهری را با استفاده از Tailwind بنویسید

💡 پیش نیازها

الزامات:

  • Node.js
  • داکر
  • هر ویرایشگر (من از کد VS استفاده خواهم کرد)

می توانید نسخه های Node و Docker را با تایپ کردن بررسی کنید:

node --version
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

docker --version
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پیشنهاد می کنم از آخرین نسخه ها استفاده کنید.

Typescript CRUD API: Next.js، TypeScript، Tailwind، tRPC، Prisma Postgres، Docker


🚀 با استفاده از برنامه Create T3 یک پروژه جدید ایجاد کنید

برای ایجاد برنامه خود، از “Create T3 App” استفاده می کنیم. این یک ابزار CLI برای ایجاد یک پروژه جدید با تمام فناوری های لازم است.

Typescript CRUD API: Next.js، TypeScript، Tailwind، tRPC، Prisma Postgres، Docker

npm create t3-app@latest
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

همه را انتخاب کنید به جز nextauth (از فاصله برای انتخاب/لغو انتخاب استفاده کنید)

Typescript CRUD API: Next.js، TypeScript، Tailwind، tRPC، Prisma Postgres، Docker

این معمولا چند دقیقه طول می کشد.

سپس وارد دایرکتوری شوید:

cd my-t3-app
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون پروژه را با استفاده از هر IDE باز کنید.
اگر از VS Code استفاده می کنید، می توانید تایپ کنید:

code .
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پروژه باید شبیه به این باشد.

Typescript CRUD API: Next.js، TypeScript، Tailwind، tRPC، Prisma Postgres، Docker

ما می توانیم برنامه را به صورت محلی با تایپ کردن آزمایش کنیم:

npm run dev
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و بازدید کنید localhost:3000

Typescript CRUD API: Next.js، TypeScript، Tailwind، tRPC، Prisma Postgres، Docker


🐳 ظرف Postgres را با استفاده از Docker اجرا کنید

بیایید یک ظرف Postgres را با استفاده از Docker اجرا کنیم.

برای این کار یک فایل جدید به نام ایجاد کنید docker-compose.yml در سطح ریشه پروژه

سپس فایل را با کد زیر پر کنید:

version: "3.9"

services:
  db:
    container_name: db
    image: postgres:12
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_DB=postgres
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata: {}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توضیح:

  • db نام سرویس واحد (کانتینر) است که ما اجرا خواهیم کرد

  • container_name نام سفارشی ما است. در این مورد، آن است db

  • image تصویری است که ما از DockerHub استفاده می کنیم. ما از Postgres نسخه 12 استفاده خواهیم کرد

  • ports نقشه برداری از پورت های خارجی-داخلی برای کانتینر است. برای جلوگیری از سردرگمی از Postgres one پیش فرض استفاده می کنیم.

  • environment این است که متغیرهای محیطی را تعریف کنیم: ما از “postgres” برای کاربر، رمز عبور و پایگاه داده استفاده خواهیم کرد (این کار را در تولید انجام ندهید!)

  • volumes این است که حجم هایی را که می خواهیم در این سرویس استفاده کنیم را اعلام کنیم. ما از یک حجم با نام استفاده می کنیم که در زیر نیز تعریف می کنیم.

حال اجازه دهید این ظرف را با استفاده از دستور اجرا کنیم:

docker compose up -d
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و بیایید بررسی کنیم که کانتینر آماده و فعال است:

docker ps -a
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Typescript CRUD API: Next.js، TypeScript، Tailwind، tRPC، Prisma Postgres، Docker

اکنون آماده هستیم تا اپلیکیشن خود را به پایگاه داده متصل کرده و طرح دیتابیس را ایجاد کنیم


🔼 اپلیکیشن را با استفاده از Prisma به پایگاه داده Postgres متصل کنید

برای اتصال برنامه ما به پایگاه داده، آن را باز کنید .env فایل را وارد کنید و محتوای زیر را جایگزین کنید:

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این فایل به .gitignore اضافه می شود و به مخزن عمومی منتقل نمی شود، بنابراین اگر پروژه را شبیه سازی کرده اید، باید این فایل را ایجاد کنید.

اکنون آن را باز کرده و ویرایش کنید prisma/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"
    previewFeatures = ["jsonProtocol"]
}

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

//User with an id, name and email as strings
model User {
    id    String @id @default(uuid())
    name  String
    email String
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما ارائه دهنده را جایگزین می کنیم postgresql و مدل با کاربر با:

همه فیلدها رشته هستند.

اکنون، برای به روز رسانی طرحواره در DB، می توانید تایپ کنید:

npx prisma migrate dev --name init
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و برای آزمایش اینکه آیا همه چیز به درستی کار می کند، می توانیم از Prisma Studio استفاده کنیم، ابزاری که با Prisma ارائه می شود. نوع:

npx prisma studio
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و باز کنید localhost:5555

سپس می توانید یک رکورد را به صورت دستی اضافه کنید. این بعداً مفید خواهد بود.

Typescript CRUD API: Next.js، TypeScript، Tailwind، tRPC، Prisma Postgres، Docker

__

⌨️ برنامه Next.js را کد کنید

📜 فایل example.ts

اکنون که پایگاه داده را راه اندازی کرده ایم و برنامه خود را متصل کرده ایم، می توانیم شروع به نوشتن کد کنیم.

باز کن src/server/api/routers/example.ts فایل را وارد کنید و محتوای زیر را جایگزین کنید:

import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";

const idSchema = z.object({ id: z.string() });

const userSchema = z.object({
  name: z.string(),
  email: z.string(),
});

const userUpdateSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string(),
});

export const exampleRouter = createTRPCRouter({
  //get all users
  getAll: publicProcedure.query(({ ctx }) => {
    return ctx.prisma.user.findMany();
  }),

  //get user by id
  getOne: publicProcedure
    .input(idSchema)
    .query(({ input, ctx }) => {
      return ctx.prisma.user.findUnique({
        where: idSchema.parse(input),
      });
    }),

  //create user
  createUser: publicProcedure
    .input(userSchema)
    .mutation(({ input, ctx }) => {
      return ctx.prisma.user.create({
        data: userSchema.parse(input),
      });
    }),

  //update user
  updateUser: publicProcedure
    .input(userUpdateSchema)
    .mutation(({ input, ctx }) => {
      return ctx.prisma.user.update({
        where: {
          id: input.id.toString(),
        },
        data: userUpdateSchema.parse(input),
      });
    }),

  //delete user
  deleteUser: publicProcedure
    .input(idSchema)
    .mutation(({ input, ctx }) => {
      return ctx.prisma.user.delete({
        where: idSchema.parse(input),
      });
    }),
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توضیح:

  • z یک کتابخانه برای اعتبارسنجی ورودی و خروجی توابع است. ما از آن برای تأیید اعتبار ورودی و خروجی توابع استفاده خواهیم کرد.

  • createTRPCRouter تابعی است که یک روتر برای ما ایجاد می کند. یک شی با توابعی که می خواهیم در معرض نمایش بگذاریم می گیرد.

  • publicProcedure تابعی است که یک طرحواره را می گیرد و تابعی را برمی گرداند که یک تابع را می گیرد.

  • idSchema طرحواره ای است که یک شی با id را به عنوان رشته می گیرد.

  • userSchema طرحی است که یک شی با نام و یک ایمیل را به عنوان رشته می گیرد.

  • userUpdateSchema طرحی است که یک شی با شناسه، نام و ایمیل را به عنوان رشته می گیرد.

  • exampleRouter روتری است که ما از آن برای نمایش توابع استفاده خواهیم کرد.

  • getAll تابعی است که همه کاربران را برمی گرداند.

  • getOne تابعی است که یک کاربر را با id برمی گرداند.

  • createUser تابعی است که کاربر ایجاد می کند.

  • updateUser تابعی است که کاربر را به روز می کند.

  • deleteUser تابعی است که کاربر را حذف می کند.

برای توضیح بیشتر به آدرس زیر مراجعه کنید: https://youtu.be/Gf9RkaHnsR8?t=406

📜 فایل index.tsx

فقط یک فایل دیگر را باید ویرایش کنیم.

باز کن src/pages/index.tsx و آن را با موارد زیر پر کنید:

import { useState } from "react";
import { api } from "~/utils/api";

export default function Home() {
  //define constants
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [nameToUpdate, setNameToUpdate] = useState("");
  const [emailToUpdate, setEmailToUpdate] = useState("");
  const [userId, setUserId] = useState("");
  const [userIdToUpdate, setUserIdToUpdate] = useState("");
  const [userIdToDelete, setUserIdToDelete] = useState("");

  //define functions
  const fetchAllUsers = api.example.getAll.useQuery();
  const fetchOneUser = api.example.getOne.useQuery({ id: userId });

  const createUserMutation = api.example.createUser.useMutation();
  const updateUserMutation = api.example.updateUser.useMutation();
  const deleteUserMutation = api.example.deleteUser.useMutation();

  //define handlers
  const handleCreateUser = async () => {
    try {
      await createUserMutation.mutateAsync({
        name: name,
        email: email,
      });
      setName("");
      setEmail("");
      fetchAllUsers.refetch();
    } catch (error) {
      console.log(error);
    }
  };

  const handleUpdateUser = async () => {
    try {
      await updateUserMutation.mutateAsync({
        id: userIdToUpdate,
        name: nameToUpdate,
        email: emailToUpdate,
      });
      setNameToUpdate("");
      setEmailToUpdate("");
      setUserIdToUpdate("");
      fetchAllUsers.refetch();
    } catch (error) {
      console.log(error);
    }
  };

  const handleDeleteUser = async () => {
    try {
      await deleteUserMutation.mutateAsync({
        id: userIdToDelete,
      });
      setUserIdToDelete("");
      fetchAllUsers.refetch();
    } catch (error) {
      console.log(error);
    }
  };

  //return an empty div
  return (
    <div className="mx-auto p-8">
      <div className="mb-8">
        <h2 className="mb-4 text-2xl font-bold">Get All Users</h2>
      </div>
      <button
        className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
        onClick={() => fetchAllUsers.refetch()}
      >
        Get All Users
      </button>

      <div className="text- mb-4 mt-4 grid grid-cols-3 gap-4 font-bold">
        <p>Id</p>
        <p>Name</p>
        <p>Email</p>
      </div>

      {fetchAllUsers.data &&
        fetchAllUsers.data.map((user) => (
          <div
            key={user.id}
            className="my-4 grid grid-cols-3 gap-4 rounded border border-gray-300 bg-white p-4 shadow"
          >
            <p>{user.id}</p>
            <p>{user.name}</p>
            <p>{user.email}</p>
          </div>
        ))}

      {/* Get one user UI */}

      <div className="mb-8">
        <h2 className="mb-4 text-2xl font-bold">Get One User</h2>
        <div className="mb-4 flex">
          <input
            className="mr-2 border border-gray-300 p-2"
            placeholder="Enter user id to get"
            value={userId || ""}
            onChange={(e) => setUserId(String(e.target.value))}
          />
          <button
            className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
            onClick={() => fetchOneUser.refetch()}
          >
            Get One User
          </button>
        </div>
        {fetchOneUser.data && (
          <div>
            <p>Name: {fetchOneUser.data.name}</p>
            <p>Email: {fetchOneUser.data.email}</p>
          </div>
        )}
      </div>

      {/* Create User */}
      <div className="mb-8">
        <h2 className="mb-4 text-2xl font-bold">Create New User</h2>
        <div className="mb-4 flex">
          <input
            className="mr-2 w-1/2 border border-gray-300 p-2"
            placeholder="Name"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
          <input
            className="w-1/2 border border-gray-300 p-2"
            placeholder="Email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </div>

        <button
          className="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600"
          onClick={handleCreateUser}
        >
          Create User
        </button>
      </div>

      {/* Update User */}
      <div className="mb-8">
        <h2 className="mb-4 text-2xl font-bold">Update User</h2>
        <div className="mb-4 flex">
          <input
            className="mr-2 w-1/2 border border-gray-300 p-2"
            placeholder="Name to update"
            value={nameToUpdate}
            onChange={(e) => setNameToUpdate(e.target.value)}
          />
          <input
            className="w-1/2 border border-gray-300 p-2"
            placeholder="Email to update"
            value={emailToUpdate}
            onChange={(e) => setEmailToUpdate(e.target.value)}
          />
        </div>
        <input
          placeholder="Enter user id to update"
          className="mr-2 border border-gray-300 p-2"
          value={userIdToUpdate}
          onChange={(e) => setUserIdToUpdate(e.target.value)}
        />
        <button
          className="mt-2 rounded bg-orange-500 px-4 py-2 text-white hover:bg-orange-600"
          onClick={handleUpdateUser}
        >
          Update User
        </button>
      </div>

      {/* Delete User */}

      <div className="mb-8">
        <h2 className="mb-4 text-2xl font-bold">Delete User</h2>
        <input
          placeholder="Enter user id to delete"
          className="mr-2 border border-gray-300 p-2"
          value={userIdToDelete}
          onChange={(e) => setUserIdToDelete(e.target.value)}
        />
        <button
          className="mt-2 rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
          onClick={handleDeleteUser}
        >
          Delete User
        </button>
      </div>
    </div>
  );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توضیح:

  • ما با استفاده از Next.js یک برنامه تک صفحه ای ایجاد می کنیم useRouter برای دریافت پارامترهای پرس و جو از URL قلاب کنید.

  • ما استفاده می کنیم useMutation قلاب از react-query برای ایجاد، به روز رسانی و حذف کاربران.

  • ما استفاده می کنیم useQuery قلاب از react-query برای واکشی همه کاربران و واکشی یک کاربر.

  • ما پنج تابع برای مدیریت عملیات CRUD داریم:

  • getAllUsers – همه کاربران را واکشی می کند

  • getOneUser – یک کاربر را واکشی می کند

  • handleCreateUser – یک کاربر جدید ایجاد می کند

  • handleUpdateUser – یک کاربر را به روز می کند

  • handleDeleteUser – یک کاربر را حذف می کند

ما برای هر تابعی که با Tailwind CSS استایل شده است یک رابط کاربری ایجاد می کنیم.

پس از هر عملیات، داده ها را دوباره واکشی می کنیم تا تغییرات را ببینیم.

به عنوان مثال، پس از ایجاد یک کاربر جدید، همه کاربران را دوباره واکشی می کنیم تا کاربر جدید را در لیست ببینند.

برای توضیح بیشتر، ویدیو را ببینید: https://youtu.be/Gf9RkaHnsR8

پروژه نهایی ما باید به این صورت باشد:

Typescript CRUD API: Next.js، TypeScript، Tailwind، tRPC، Prisma Postgres، Docker

این پروژه به عنوان نمونه در نظر گرفته شده است تا بتوانید از آن به عنوان نقطه شروع برای پروژه های خود استفاده کنید. اگر می‌خواهید مشارکت داشته باشید، با خیال راحت یک روابط عمومی در GitHub باز کنید (لینک در توضیحات ویدیو).

__

🏁 نتیجه گیری

ما یک CRUD API با استفاده از فناوری‌های زیر ساختیم:

  • Next.js
  • TypeScript
  • Tailwind CSS
  • tRPC
  • پریسما
  • Postgres
  • داکر

اگر نسخه ویدیویی را ترجیح می دهید:

https://www.youtube.com/watch?v=Gf9RkaHnsR8

همه کدها به صورت رایگان در GitHub در دسترس هستند (لینک در توضیحات ویدیو).

در اینجا می توانید با فرانچسکو ارتباط برقرار کنید

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا