ブリッツ.JS : Fullstack反応フレームワークパート2
歓迎する👋
おい、開発者、シリーズの第2の部分へようこそ.
Check part one if you haven't already:
前の部分では、我々は新鮮な電撃を設定完了しました.JSプロジェクトは、レシピを使用してTarwind CSSを追加し、データベースモデルを作成し、このプロジェクトに必要なファイルを生成しました.
今日、スキーマファイルを更新することから始めましょう.
では、始めましょう.
インデックス
Check part one if you haven't already:
Understanding and updating Logics
Building UI
データベーススキーマの更新
前の記事では、プロジェクトとタスクテーブルの関係を作成しましたが、タスク名とタスクの記述を格納するフィールドを作成していません.まず最初に、スキームを更新しましょう.必須のフィールドを持つPRIMAファイル.
// file: db/schema.prisma
...
model Project {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String
description String
tasks Task[]
}
model Task {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
project Project @relation(fields: [projectId], references: [id])
projectId Int
// Add the following new fields
name String
description String?
}
あなたが我々が変わったものに気がつかなかったならば、チェックしてくださいTask
我々が加えたモデルname
フィールドオブString
種類description
of String
ヌルブルで?
.
それはschema
.
コマンドを実行するblitz prisma migrate dev
. そして、マイグレーションのためにどんな名前も与えます、しかし、我々が2つの新しいフィールドを加えることによって、我々はタスクテーブルを更新したので、私はそれに名前をつけますupdate_tasks_table
. あなたがPrisma Studioを開くならばblitz prisma studio
, タスクテーブルに2つの新しいフィールドが表示されます.
論理を作りましょう.
論理の理解と更新
私たちは、突然変異とクエリを理解して、データベース内のデータを変更し、以前の部分からコード足場によって生成されたデータベースからデータを取得しますが、新しいフィールドを追加したので、突然変異や論理を更新しなければなりません.
プロジェクトの論理
まず、プロジェクトのCRUD操作を作成しましょう.
オープンapp/projects/mutations/createProject.ts
を追加します.
// app/projects/mutations/createProject.ts
import { resolver } from "blitz"
import db from "db"
import { z } from "zod"
const CreateProject = z.object({
name: z.string(),
description: z.string(),
})
export default resolver.pipe(
resolver.zod(CreateProject), // This is a handly utility for using Zod, an awesome input validation library. It takes a zod schema and runs schema.parse on the input data.
resolver.authorize(), // Require Authentication
async (input) => {
// Create the project
const project = await db.project.create({ data: input })
// Return created project
return project
}
)
コードを分割し、各ラインを理解しましょう.
// file: db/schema.prisma
...
model Project {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String
description String
tasks Task[]
}
model Task {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
project Project @relation(fields: [projectId], references: [id])
projectId Int
// Add the following new fields
name String
description String?
}
私たちは、突然変異とクエリを理解して、データベース内のデータを変更し、以前の部分からコード足場によって生成されたデータベースからデータを取得しますが、新しいフィールドを追加したので、突然変異や論理を更新しなければなりません.
プロジェクトの論理
まず、プロジェクトのCRUD操作を作成しましょう.
オープン
app/projects/mutations/createProject.ts
を追加します.// app/projects/mutations/createProject.ts
import { resolver } from "blitz"
import db from "db"
import { z } from "zod"
const CreateProject = z.object({
name: z.string(),
description: z.string(),
})
export default resolver.pipe(
resolver.zod(CreateProject), // This is a handly utility for using Zod, an awesome input validation library. It takes a zod schema and runs schema.parse on the input data.
resolver.authorize(), // Require Authentication
async (input) => {
// Create the project
const project = await db.project.create({ data: input })
// Return created project
return project
}
)
コードを分割し、各ラインを理解しましょう.import { resolver } from "blitz"
: ブリッツはいくつかのユーティリティを含むリゾルバオブジェクトをエクスポートします.ここで使用される「レゾルバ」とは、いくつかの入力を取り、いくつかの出力または副作用に「分解する」機能を意味する.Click here to know more import db from "db"
: ヒアdb
によって強化されたPrismaクライアントblitz
. import { z } from "zod"
: ZODはタイプスクリプトの最初のスキーマ宣言と検証ライブラリです.私は、単純なストリングから複雑な入れ子になったオブジェクトまでどんなデータ型でも広く参照するために「スキーマ」という用語を使用しています.Click here to know more const CreateProject
: CreateProject
指定した入力が含まれているかどうかを検証するオブジェクトスキーマですname
フィールドオブstring
種類description
' string '型のフィールド.resolver.pipe
: これは複雑なリゾルバを書くことをより簡単で、よりきれいにする機能的なパイプです.パイプは、1つの関数の出力を自動的に次の関数にパイプします.( Blitz.js Docs ) resolver.zod(CreateProject)
: これは、ZOD、素晴らしい入力検証ライブラリを使用するための便利なユーティリティです.これは、ZODスキーマと実行スキーマを取得します.入力データをパースします.( Blitz.js Docs ) resolver.authorize()
: リゾルバの使用解雇リゾルバ.パイプは、ユーザーがクエリまたは突然変異を呼び出す権限を持っているかどうかを確認する簡単な方法です.( Blitz.js Docs ) async (input) => {}
: このasync関数はコールバックです.db.project.create
: データベースに新しいプロジェクトを作成します.return project
: 作成したデータを返します.プロジェクトを取得するロジックを構築しましょう.
// file: app/projects/queries/getProjects.ts
import { paginate, resolver } from "blitz"
import db, { Prisma } from "db"
interface GetProjectsInput
extends Pick<Prisma.ProjectFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
export default resolver.pipe(
resolver.authorize(),
async ({ where, orderBy, skip = 0, take = 100 }: GetProjectsInput) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const {
items: projects,
hasMore,
nextPage,
count,
} = await paginate({
skip,
take,
count: () => db.project.count({ where }),
query: (paginateArgs) =>
db.project.findMany({ ...paginateArgs, where, orderBy, include: { tasks: true } }),
})
return {
projects,
nextPage,
hasMore,
count,
}
}
)
このファイルでは、1つの変更を行い、私はinclude
オプションdb.project.findMany()
.これは、それぞれのプロジェクトに属するすべてのタスクを含みます.
さて、このコードの各ラインを理解しましょう.私は、私がすでに建設しているものを繰り返すつもりでありません
create project
ロジック.インポートもスキップします.interface GetProjectsInput
extends Pick<Prisma.ProjectFindManyArgs, "where" | "orderBy" | "skip" | "take"> {}
: これを行うには、プロパティのセットを選択してインターフェイスを作成しますPrisma.ProjectFindManyArgs
. ( TS Docs ) Prisma.ProjectFindManyArgs
: Prismaはモデルと引数の型を生成します.ここではPrismaによって生成されたProjectFindManallers `を使用します.paginate
: これは、クエリのページ付けの便利なユーティリティです.( Blitz.js Docs ). db.project.count({where})
: 引数が通過した条件に続くデータベースからのデータ数を返します.( Prisma Docs ) db.project.findMany()
: すべてのデータをプロジェクト表から取得します.これをもとに生成したものと比較すれば、私たちはinclude
オプションを指定します.からこのテーブルに属するすべてのタスクを取得します.`
// app/projects/queries/getProject.ts
import { resolver, NotFoundError } from "blitz"
import db from "db"
import { z } from "zod"
const GetProject = z.object({
// This accepts type of undefined, but is required at runtime
id: z.number().optional().refine(Boolean, "Required"),
})
export default resolver.pipe(resolver.zod(GetProject), resolver.authorize(), async ({ id }) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const project = await db.project.findFirst({ where: { id }, include: { tasks: true } })
if (!project) throw new NotFoundError()
return project
})
`
.refine()
: ( ZOD Docs )
-
db.project.findFirst()
: 与えられた条件を満たす最初のデータを返します.( Prisma Docs ) -
throw new NotFoundError()
: 404エラーをスローします. さて、プロジェクトを更新するロジックを見ましょう.`
// app/projects/mutations/updateProject.ts
import { resolver } from "blitz"
import db from "db"
import { z } from "zod"
const UpdateProject = z.object({
id: z.number(),
name: z.string(),
description: z.string(),
})
export default resolver.pipe(
resolver.zod(UpdateProject),
resolver.authorize(),
async ({ id, ...data }) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const project = await db.project.update({ where: { id }, data })
return project
}
)
`
-
db.project.update()
: 指定したIDを持つプロジェクト行のデータを更新します.Prisma Docs ) 最後に、ロジックがプロジェクトを削除するまでの時間です.`
// app/projects/mutations/deleteProject.ts
import { resolver } from "blitz"
import db from "db"
import { z } from "zod"
const DeleteProject = z.object({
id: z.number(),
})
export default resolver.pipe(resolver.zod(DeleteProject), resolver.authorize(), async ({ id }) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const tasks = await db.task.deleteMany({ where: { projectId: id } })
const project = await db.project.deleteMany({ where: { id } })
return project
})
`
If you look there, I have added a new line const tasks = = await db.task.deleteMany({ where: { projectId: id } })
. これは、最初にそのプロジェクトに属するすべてのタスクを削除し、その後、実際のプロジェクトが削除されました.
-
db.project.deleteMany
: 指定した基準を満たすテーブルから行を削除します. さて、プロジェクトのためのCRUDは完成しました、現在、それはタスクのcrud操作のための時間です.
タスクの論理
新しいタスクを作成するためのタスクロジックを更新しましょう.`
// app/tasks/mutations/createTask.ts
import { resolver } from "blitz"
import db from "db"
import { z } from "zod"
const CreateTask = z.object({
name: z.string(),
projectId: z.number(),
// This is what we have added
description: z.string().optional(),
})
export default resolver.pipe(resolver.zod(CreateTask), resolver.authorize(), async (input) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const task = await db.task.create({ data: input })
return task
})
`
Everything looks familiar, Nah. We have already discussed the syntax used up here before.
After we created tasks, we need to retrieve the tasks, so let getAll the tasks.
`
// app/tasks/queries/getTasks.ts
import { paginate, resolver } from "blitz"
import db, { Prisma } from "db"
interface GetTasksInput
extends Pick {}
export default resolver.pipe(
resolver.authorize(),
async ({ where, orderBy, skip = 0, take = 100 }: GetTasksInput) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const {
items: tasks,
hasMore,
nextPage,
count,
} = await paginate({
skip,
take,
count: () => db.task.count({ where }),
query: (paginateArgs) => db.task.findMany({ ...paginateArgs, where, orderBy }),
})
return {
tasks,
nextPage,
hasMore,
count,
}
}
)
`
Everything is the same up here as generated.
Let's see the mutation to update the task.
`js
// app/tasks/mutations/updateTask.ts
import { resolver } from "blitz"
import db from "db"
import { z } from "zod"
const UpdateTask = z.object({
id: z.number(),
name: z.string(),
// The only thing we have added
description: z.string().optional(),
})
export default resolver.pipe(
resolver.zod(UpdateTask),
resolver.authorize(),
async ({ id, ...data }) => {
// TODO: in multi-tenant app, you must add validation to ensure correct tenant
const task = await db.task.update({ where: { id }, data })
return task
}
)
`
For the getTask
クエリとdelete
突然変異、そのまま残す.
現在、我々は論理のためにされます.
ビルのUI
既にインストール済みです Tailwind CSS
前の部分のブリッツレシピで.( Read it here ). それで、我々はこのプロジェクトのためにTailwind CSSライブラリを使用しています.そして、私たちはTailWindCSSを使用して簡単なUIを作成します.
サインアップページコンポーネント
リンク/signup
オープンapp/auth/pages/signup.tsx
. カスタムコンポーネントを使用していることがわかりますSignupForm
フォームに.だから、それを開きますapp/auth/components/SignupForm.tsx
. それから、あなたは彼らがカスタムを使っているのを見るでしょうForm Component
and LabeledTextField
コンポーネント.
だから我々の最初の仕事をカスタマイズすることですForm
and LabeledTextFieldComponent
.
オープンapp/core/Form.tsx
追加p-5 border rounded
クラスでform
タグと追加text-sm
クラスalert
. `jsx
// app/core/components/Form.tsx
{submitError && (
{submitError}
)}
...
...
`
Now, let's customize LabeledTextFieldComponent
.
これについては、まず最初に、スタイル風クラスを持つ入力のカスタムコンポーネントを作成します.
移動するapp/core/components
ファイルを開くLabeledTextField.tsx
そして、次のコードでそれを更新します.`jsx
// app/core/components/LabeledTextField.tsx
import { forwardRef, PropsWithoutRef } from "react"
import { useField } from "react-final-form"
export interface LabeledTextFieldProps extends PropsWithoutRef {
/** Field name. /
name: string
/* Field label. /
label: string
/* Field type. Doesn't include radio buttons and checkboxes */
type?: "text" | "password" | "email" | "number"
outerProps?: PropsWithoutRef
}
export const LabeledTextField = forwardRef(
({ name, label, outerProps, ...props }, ref) => {
const {
input,
meta: { touched, error, submitError, submitting },
} = useField(name, {
parse: props.type === "number" ? Number : undefined,
})
const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError
return (
<div {...outerProps}>
<label className="flex flex-col items-start">
{label}
<input
{...input}
className="px-1 py-2 border rounded focus:ring focus:outline-none ring-purple-200 block w-full my-2"
disabled={submitting}
{...props}
ref={ref}
/>
</label>
{touched && normalizedError && (
<div role="alert" className="text-sm" style={{ color: "red" }}>
{normalizedError}
</div>
)}
</div>
)
}
)
export default LabeledTextField
`
Always remember that the components that are required for a specific model, we have to create that inside the components folder in that model, for example. if we want a form to create a project then we add that form component inside app/project/components
. But if that component is not model specific, then we create those components inside app/core/components
.
Let's create a new core Button
コンポーネントは、サイトの至る所で使用する.`jsx
// app/core/components/Button.tsx
export const Button = ({ children, ...props }) => {
return (
{children}
)
}
`
Now let's use this new Button
コンポーネントForm.tsx
.
In app/core/components/Form.tsx
置換
{submitText && (
<button type="submit" disabled={submitting}>
{submitText}
</button>
)}
with
{submitText && (
<Button type="submit" disabled={submitting}>
{submitText}
</Button>
)}
And don't forget to import the Button
.
import { Button } from "./Button"
Now, you should have something like this.
Let's customize this page more.
We'll use a separate layout for the authentication pages. So, go to app/core/layouts
という名前の新しいファイルを作成しますAuthLayout.tsx
次のコンテンツを追加します.`
// app/core/layouts/AuthLayout.tsx
import { ReactNode } from "react"
import { Head } from "blitz"
type LayoutProps = {
title?: string
heading: string
children: ReactNode
}
const AuthLayout = ({ title, heading, children }: LayoutProps) => {
return (
<>
{title || "ProjectManagement"}
<div className="flex justify-center">
<div className="w-full md:w-2/3 lg:max-w-2xl mt-5">
<h2 className="text-xl mb-2">{heading}</h2>
<div>{children}</div>
</div>
</div>
</>
)
}
export default AuthLayout
`
Now go to the SignupForm
コンポーネントの削除h1
タグ.除去後
<h1>Create an Account</h1>
ファイルは次のようになります.`
import { useMutation } from "blitz"
import { LabeledTextField } from "app/core/components/LabeledTextField"
import { Form, FORM_ERROR } from "app/core/components/Form"
import signup from "app/auth/mutations/signup"
import { Signup } from "app/auth/validations"
type SignupFormProps = {
onSuccess?: () => void
}
export const SignupForm = (props: SignupFormProps) => {
const [signupMutation] = useMutation(signup)
return (
<Form
submitText="Create Account"
schema={Signup}
initialValues={{ email: "", password: "" }}
onSubmit={async (values) => {
try {
await signupMutation(values)
props.onSuccess?.()
} catch (error) {
if (error.code === "P2002" && error.meta?.target?.includes("email")) {
// This error comes from Prisma
return { email: "This email is already being used" }
} else {
return { [FORM_ERROR]: error.toString() }
}
}
}}
>
<LabeledTextField name="email" label="Email" placeholder="Email" />
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
</Form>
</div>
)
}
export default SignupForm
`
Now, we have to tell signup
使用するページAuthLayout
レイアウトとして.
そのためにはapp/auth/pages/signup.tsx
そして、変更する行を変更します.
SignupPage.getLayout = (page) => <Layout title="Sign Up">{page}</Layout>
to
SignupPage.getLayout = (page) => <AuthLayout heading="Create an account" title="Sign Up">{page}</AuthLayout>
and import AuthLayout
.
import AuthLayout from "app/core/layouts/AuthLayout"
Now, your signup page should look like this.
⚠️ Ignore that LastPass sign in the input field.
Let's include a link to go to the login page in the signup page.
For this, we'll create our own custom Link component with tailwind style.
Go to /app/core/components
と新しいファイルを作成するCustomLink.tsx
を追加します.`
// app/core/components/CustomLink.tsx
import { Link } from "blitz"
export const CustomLink = ({ children, href }: any) => {
return (
{children}
)
}
`
Now, to include the go-to login link you have to add the following line after the Form
タグ.`
...
Already have account? Login
`
After all this, your signup page should look like this.
Now, since we have already styled many components in the SignUp UI
セクションは、他のページでは、あまりにも多くの仕事をする必要はありません他のページです.
ログインページ
ログイン/ログイン
ログインページのカスタマイズでは、次の行を置き換えますlogin.tsx
:`
// app/auth/pages/login
LoginPage.getLayout = (page) => {page}
`
to
LoginPage.getLayout = (page) => (
<AuthLayout heading="Welcome back, login here" title="Log In">
{page}
</AuthLayout>
)
and import AuthLayout
.
import AuthLayout from "app/core/layouts/AuthLayout"
After doing this, your login page should look like this.
Now, remove <h1>Login</h1>
からapp/auth/components/LoginForm.tsx
.
また、以下の行を置き換えますLoginForm.tsx
:`
// from
Forgot your password?
// to
Forgot your password?
`
and
`
// from
Sign Up
// to
Sign Up
`
After getting up to this, your login page should look like.
パスワードのページ
Link : '/forgot-password'
As before, change the layout to AuthLayout
.`
// app/auth/pages/forgot-password.tsx
import AuthLayout from "app/core/layouts/AuthLayout"
...
ForgotPasswordPage.getLayout = (page) => (
{page}
)
`
and remove <h1>Forgot your password?</h1>
からapp/auth/pages/forgot-password.tsx
.
今、忘れられたパスワードページが行われ、それはように見えるはずです.
今、最終的に認証の最終ページ.
パスワードのリセット
パスワードをリセット
レイアウトを変更する前にAuthLayout
.`
// app/auth/pages/reset-password.tsx
import AuthLayout from "app/core/layouts/AuthLayout"
...
ResetPasswordPage.getLayout = (page) => (
{page}
)
`
and remove <h1>Set a New Password</h1>
そして、それはこのように見えるはずです.
これは今日の人々のため.
回収する - スキーマの更新
- TruWindCSSを使用した認証ページの編集UI
- カスタムコンポーネント
- AuthLayoutの作成と使用
Reference
この問題について(ブリッツ.JS : Fullstack反応フレームワークパート2), 我々は、より多くの情報をここで見つけました https://dev.to/chapagainashik/blitz-js-the-fullstack-react-framework-part-2-4697テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol