👌 Blitz.jsで古臭いBBSを作るやつのメモ #01
#02: https://zenn.dev/hanak1a/scraps/98e7cf2e25f708
一通りのCRUDが出来たので一旦〆
感想としてはこんな感じ
追記: 2021/01/02
- APIのルートは自分で定義できるっぽいので、型付きでシュッと始められるAPIサーバーとしてはBlitzは結構有力そう
- ただし、公式ドキュメントを斜め読みした感じ、ActiveRecordにおけるModelみたいな、「ドメインロジックを担保する層」がまだないので、Mutationsあたりの設計をどうするかというのがアプリ開発者の課題になりそう
インストールは500万のWebサイトに書いてある(大嘘)からまず公式ドキュメント通りやれよな
手癖でMySQLにつなごうとして schema.prisma
と .env.local
を触ってみたけどなんか繋がらんから放置!!! 本番だったらMySQLにしたいけどここは本番ではないのでSQLiteでやる。
initial-migrationにはUserとSessionのモデルが定義されている(今現在)
とりあえずEntryモデルを雑に生やす。 構文は定義済みモデルの見様見真似
model Entry {
id Int @id @default(autoincrement())
user_id BigInt
content String
created_at DateTime @default(now())
updated_at DateTime @updatedAt
user User @relation(fields: [user_id])
}
ここまで書いて気づいたらUserモデルにrelationが勝手に生えてた…
お前、もしかして"文明"なのか…?? VSCodeにPrisma Extension入れたら保存時にフィールド名とか見てなんかよしなにやるっぽいな…
created_at
が@default(now())
なのが気になる。 これはBlitzかPrisma側が実行時に設定してくれるのか、それともDBのスキーマに設定されるのか…(後者だとAP/DBでタイムゾーンが異なったときに死ぬという問題に当たりそう)
ちょっと待って、schema.prisma
2つあるけどどっちに設定したらええねん…
→ 落ち着いて公式ドキュメントを読みましょう。 これを書いてる時点ではdb/schema.prisma
が正解っぽいですね
blitz db migrate
したらmigrations以下のやつが勝手に生成された……
文明だ……
初めてBlitz.jsでアプリを作りました! よろしくお願いします!
はい通過儀礼。
Blitzくん、なんとユーザー登録とログインは最初から実装済み。
2021年ですね。
じゃあなんか雑にページを作っていきましょう。
サモン! app/pages/latest.tsx
!!!!!
(間違えて app/auth/pages
にファイルをブチ込む音)
Pageは見たところ普通のNext.jsっぽいですね。
違いはComponent.getLayout
とかいう見たことないメソッドが生えてるくらいかな。
getLayout
は_app.tsx
内で呼び出されてるので、アプリ開発者が任意で拡張したものと捉えて良さそう
と思ったけど、Blitzの型定義ファイルの中にgetLayout
の定義があるのを見るに、Blitz公式としてこのメソッドを提供したいようだ
PageコンポーネントはBlitzPage
型を使えばよいっぽい
import Layout from "app/layouts/Layout"
import { BlitzPage } from "blitz"
const Latest: BlitzPage = () => {
return null
}
Latest.getLayout = (page) => <Layout title="">{page}</Layout>
export default Latest
じゃあデータを取りに行きます
app/hooks
、名前が強い(Hooksがもうフレームワークのコアにいるんだなぁという気持ちにより)
ドキュメントを見るのが面倒なので、初期コードの中にそれっぽいコードがないかな〜と探索。 pages/index.tsx
からuseCurrentUser()
を呼んでいるっぽい。
その下にはuseMutation(logout)
がある。
useCurrentUserの定義ファイル(app/hooks/useCurrentUser.ts
)をみるとこの通り
export const useCurrentUser = () => {
const [user] = useQuery(getCurrentUser, null)
return user
}
useQueryってやつがReact向けのバインディングで、getCurrentUserがDBからデータを取っていそう。 こいつの中身はapp/users/queries/getCurrentUse.tsr
に居る。
import { Ctx } from "blitz"
import db from "db"
export default async function getCurrentUser(_ = null, { session }: Ctx) {
if (!session.userId) return null
const user = await db.user.findFirst({
where: { id: session.userId },
select: { id: true, name: true, email: true, role: true },
})
return user
}
つまりそういうことだ(どういうこと????)
は〜いじゃあgetLatestEntries作りま〜〜〜す。
Blitzくんのおすすめディレクトリ構造気に入らないから僕はdomains/entries
に作ってみます〜〜〜〜 壊れたら従いま〜〜〜す
import db from "db"
export default async function getLatestEntries(_ = null) {
const entries = await db.entry.findMany()
return entries
}
db.
と打った時点でヌルッと.entry
の補完が出てきて「どうやってんだこれ…」と思って'db'
の参照元を見たらapp/db/index.ts
に繋がってた… 何を言ってるかわからねえと思うが以下略
そしてgetCurrentUser
の時は第二引数に{session}
を取っていたけど、これはuseQuery(query, context)
のcontext
に渡したものが来るっぽい?
わかるよ、そのAPI気持ちいいよね(Fleurで第二引数以降のパスをやった人の顔)
Hooksもぬるりと作ります
import getLatestEntries from "app/domains/entries/query"
import { useQuery } from "blitz"
export const useLatestEntries = () => {
const [entries] = useQuery(getLatestEntries, null)
return entries
}
oO (Mizchiさんの記事でサーバーサイドコードが出ないようになってるみたいな話を聞いた気がするけど別のフレームワークの話だったかな… あとから調べてみよう…
ドウシテ……
おい!!!!!!!!!お前お前お前お前お前〜〜〜〜〜〜〜〜〜〜〜!!!!!!!!!!!!!!(pages/index.tsx
を開いた顔)
わかるよ(お気持ちの生き物)
あーそれでindex.tsx
の中でHome
とUserInfo
が別れてたのもあるのか
(Suspenseは親に居ないといけないので……)
どのリージョンをSuspenceにしたいかは完全にアプリの要件依存なので、フレームワーク開発者としては勝手にフォールバックはできんわな……
自分、涙、いいすか(男泣き)
解説: 同じ import { useQuery } from "blitz"
しているコードなのにgetCurrentUser()
は生きててgetLatestEntries()
は死んでいるぞ!!!
ディレクトリ構造をapp/entries/queries/getLatestEntries.ts
にしたら動きました。良い子は規約に従いましょう :sob:
(/app
の直下にディレクトリ増えまくるのかなり嫌な気持ちになるのはRailsに飼いならされてるからなのか…?)
はい、じゃあ次は投稿を作っていきましょうね
さっき作った(絶望)app/entries
にmutations/createEntry.ts
を作る
import db, { Prisma } from "db"
export const createEntry = async ({ data }: { data: Prisma.EntryCreateArgs }) => {
return await db.entry.create(data)
}
BlitzのドキュメントにはProjectCreateArgs
を直接importするよう記載があるけど、生成されたコードではdeprecatedになっていたのでPrisma
をimportするようにしている。
ここで Write Query Resolversを読んで「アッ、app/*/queries
に入ってれば何でもよい、なるほどね」となっています
書き込むページ作った(動作検証してない)
useMutation(mutationFunction)
するとmutationするやつが得られるらしい
Entryに紐づくUserの型が通らなくて一瞬唸ったが、補完リストを見るとどうやらObjectのキーでリレーションに対して「つなげる・つくる・なかったらつくる」を切り分けられるらしい。
頭いいな(ほんとか? Componentにこんな大事な処理が散らかってたら「Railsもびっくり!型スラム!!!」になってしまうので多分ここらへんもうまいことmutation関数の中に閉じ込めないといつか死ぬぞ。 アプリ設計者の僕たちの頑張りどころだね!守ろう治安!!!)
import { createEntry } from "app/entries/mutations/createEntry"
import { useCurrentUser } from "app/hooks/useCurrentUser"
import { BlitzPage, useMutation } from "blitz"
import { useCallback, useRef } from "react"
const New: BlitzPage = () => {
const [createEntryMutation] = useMutation(createEntry)
const currentUser = useCurrentUser()
const textareaRef = useRef<HTMLTextAreaElement>(null)
const onSubmit = useCallback(async () => {
if (!currentUser) return
await createEntryMutation({
data: {
data: {
content: textareaRef.current?.value!,
user: { connect: currentUser },
},
},
})
}, [])
return (
<div>
<textarea ref={textareaRef} placeholder="最後に言いたいことは?" />
<button onClick={onSubmit} />
</div>
)
}
export default New
ドウシテ…
- Mutations must export a function as the default export
https://blitzjs.com/docs/mutation-resolvers
はい……
Suspence入れてなくて死んだので直した、だるびっしゅ…
import createEntry from "app/entries/mutations/createEntry"
import { useCurrentUser } from "app/hooks/useCurrentUser"
import { BlitzPage, useMutation } from "blitz"
import { Suspense, useCallback, useRef } from "react"
const Content = () => {
const [createEntryMutation] = useMutation(createEntry)
const currentUser = useCurrentUser()
const textareaRef = useRef<HTMLTextAreaElement>(null)
const onSubmit = useCallback(async () => {
if (!currentUser) return
await createEntryMutation({
data: {
data: {
content: textareaRef.current?.value!,
user: { connect: currentUser },
},
},
})
}, [currentUser])
return (
<div>
<textarea ref={textareaRef} placeholder="最後に言いたいことは?" />
<button onClick={onSubmit}>以上です</button>
</div>
)
}
const New: BlitzPage = () => {
return (
<Suspense fallback="matte">
<Content />
</Suspense>
)
}
export default New
mutationの外部キーにはuniqueなフィールドだけを渡しましょう(懺悔)
user: { connect: { id: currentUser.id } },
Blitz完全に理解したわ、余裕余裕!!!!
#02 はこちら