World Of Zono

Latest

2025/03/30

フォームコンポーネントの実装メモ(shadcn/ui を使用)

bulletproof-react のフォームコンポーネントの実装が綺麗だったためメモメモ。 フォームコンポーネントの実装。 const Form = < Schema extends ZodType, TFormValues extends FieldValues = z.infer, >({ onSubmit, children, className, options, id, schema, }: FormProps) => { const form = useForm({ ...options, resolver: zodResolver(schema) }); return ( {children(form)} // フォームの機能を子コンポーネントに渡す!! ); }; こんな感じで使用する。 { updateDiscussionMutation.mutate({ data: values, discussionId, }); }} options={{ defaultValues: { title: discussion?.title ?? '', body: discussion?.body ?? '', public: discussion?.public ?? false, }, }} schema={updateDiscussionInputSchema} > {({ register, formState, setValue, watch }) => ( // 渡された機能を使ってフォームの実装が可能になる!!便利!! <> ~~~ {children(form)} を使って UI コンポーネント上でインポートした React Hook Form の機能を子コンポーネントに渡すことで、無駄なインポートをせずにフォームの開発が行えます。かなり汎用的ですね。すごい...。

2025/03/29

HTTP ステータスコードの定番まとめ

バックエンドの開発をしているといつもステータスコードに悩まされます。その度にググっているのですが付け焼き刃の知識なのですぐ忘れるんですよね。なので自分用に AI の結果をそのまま貼り付けました。 バックエンドエンジニアが知っておくべきHTTPステータスコード HTTPステータスコードは、クライアントとサーバー間の通信状態を表す3桁の数字です。バックエンドエンジニアとして、以下のステータスコードは必須知識です。 1. 2xxシリーズ (成功) 200 OK リクエストが成功し、レスポンスにデータが含まれている GET リクエストの標準的な成功応答 201 Created リソース作成に成功(POST成功時) 例: 新しいユーザーやエントリーの作成成功時 204 No Content リクエストは成功したが、返すコンテンツはない 例: DELETE操作成功時など 2. 4xxシリーズ (クライアントエラー) 400 Bad Request リクエストの構文が不正、またはバリデーションエラー 例: フォームデータの検証失敗時 401 Unauthorized 認証が必要(認証情報がない、または不正) 例: ログインが必要な API へ未認証でアクセス時 403 Forbidden 認証済みだが、権限がない 例: 管理者専用エンドポイントへの一般ユーザーアクセス時 404 Not Found リクエストされたリソースが存在しない 例: 存在しないIDのデータ取得時 409 Conflict リソースの現在の状態と競合する操作 例: 同じメールアドレスでユーザー登録時 422 Unprocessable Entity リクエストは構文的に正しいが、意味的に処理できない 例: 論理的に矛盾するデータや、ビジネスルール違反時 429 Too Many Requests クライアントからのリクエスト数が制限を超えた 例: レート制限超過時 3. 5xxシリーズ (サーバーエラー) 500 Internal Server Error サーバー内部でエラーが発生した 例: 未処理の例外、データベースエラーなど 502 Bad Gateway ゲートウェイやプロキシとして動作するサーバーが上流サーバーから無効なレスポンスを受けた 例: マイクロサービスアーキテクチャで別サービスがダウン時 503 Service Unavailable サーバーが一時的に要求を処理できない 例: メンテナンス中やサーバー過負荷時 504 Gateway Timeout ゲートウェイやプロキシが上流サーバーからレスポンスを適切な時間内に受け取れなかった 例: タイムアウト発生時 適切なステータスコードの使い分け方 以下のような基本原則で使い分けましょう: リソースの作成 成功: 201 Created 検証エラー: 400 Bad Request または 422 Unprocessable Entity 認証エラー: 401 Unauthorized 権限エラー: 403 Forbidden リソースの取得 成功: 200 OK 存在しない: 404 Not Found 認証エラー: 401 Unauthorized 権限エラー: 403 Forbidden リソースの更新 成功: 200 OK または 204 No Content 検証エラー: 400 Bad Request 存在しない: 404 Not Found リソースの削除 成功: 204 No Content 存在しない: 404 Not Found または 204 No Content(冪等性を保つため) 正確なHTTPステータスコードの使用は、APIの品質と使いやすさを大きく向上させます。 ずっとこの表に頼ってしまいそう。。。

2025/03/25

TypeScript の個人的メモ

Bulletproof-react のソースコードを眺めていたらよくわからない型定義を発見したので、自分なりに調べて解説します。 export type ApiFnReturnType Promise = Awaited ; export type MutationConfig Promise = UseMutationOptions< ApiFnReturnType, Error, Parameters [0] >; 型定義は1つずつ分解してみていくと意外と読み解けます。上の型定義から見ていきましょう。 まずはこちら。 Promise これはジェネリクスで渡される関数(FnType)は非同期関数であればなんでも OK という意味です。(...args: any) は可変超引数のことですね。 次はこちら。 Awaited ; これは直感的にわかるかと思います。ジェネリクスで渡された非同期関数の解決済みの値(await して取得した値)の型を定義するという意味です。ちなみに ReturnType と Awaited は TypeScript のユーティリティ型です。 つまり、『ジェネリクスで渡された非同期関数の返却値の型を使って型定義する』という意味になります。実際に動かしてみるとこのような結果になります。 type ApiFnReturnType Promise = Awaited ; // 非同期関数 const asyncFunc = (): Promise => { return new Promise((resolve) => resolve(null as unknown as T)); }; // 通常の関数 const syncFunc= () => { return 'string'; }; const stringValue: ApiFnReturnType ; // stringValue は string 型 const objectValue: ApiFnReturnType ; // objectValue は {name: string, age: number} 型 const ng: ApiFnReturnType; // ERROR!! 汎用的な関数やクラスのコールバックに設定すると便利そうですね。実際にそれが行われているのが残りのこれ。 export type MutationConfig Promise = UseMutationOptions< ApiFnReturnType, Error, Parameters [0] >; ジェネリクスの MutaionFnType は先ほど説明した通り非同期関数を意味します。UseMutationOptions の説明は省きますが、この型付けをうまいこと利用して TanStack Query のミューテーションにより強固な型付けを行なっております。ちなみに、Prameter もTypeScript のユーティリティ型で、ジェネリクスで渡された関数の引数の型のタプルを定義するものです。 UseMutationOptions< ApiFnReturnType, // TData Error, // TError Parameters [0] // TVariables >; // ↕︎ 各ジェネリクスに紐づいている type UseMutationOptions; 汎用的に作るって難しいですね。

2025/03/19

NextAuth のセッション管理を JWT に変更する

NextAuth で認証して、取得したユーザー情報を PrismaAdapter 経由で Vercel Storage に登録するところまでは実装できたのですが、Next.js の Middleware 上で認証情報を取得したところエラーが発生しました。 [auth][details]: {} [auth][error] SessionTokenError: Read more at https://errors.authjs.dev#sessiontokenerror [auth][cause]: Error: PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in ``). なんじゃこりゃ。調べてみたところ、Next.js の Middleware の実行環境(Edge Runtime)から Vercel Storage などの DB は参照できないらしい。Next.js の公式ドキュメント にもそれっぽい記述がありました。(Usage の一番下の項目) アダプターを使用するとセッション情報が DB 管理となるため詰んでね?と思いつつ、ドキュメントを読み漁っていると JWT によるセッション管理に変更できるとのことです。ありがたや。Auth.js の公式ドキュメント 通り実装すればあっという間に変更できました。 ざっと作業の流れをメモすると、プロジェクトに auth.config.ts を追加する。 import type { NextAuthConfig } from "next-auth"; import GitHub from "next-auth/providers/github"; export default { providers: [GitHub] } satisfies NextAuthConfig; NextAuth にパラメータを追加。 import { prisma } from "@/prisma"; import { PrismaAdapter } from "@auth/prisma-adapter"; import NextAuth from "next-auth"; import authConfig from "../auth.config"; export const { signIn, signOut, auth, handlers } = NextAuth({ adapter: PrismaAdapter(prisma), session: { strategy: "jwt", }, ...authConfig, }); これで Middleware から参照可能に。 import NextAuth from "next-auth"; import { type NextRequest, NextResponse } from "next/server"; import authConfig from "../auth.config"; const { auth } = NextAuth(authConfig); export default async function middleware(req: NextRequest) { const session = await auth(); // 参照可能! ... これだけです。JWT に移行すると DB のセッションテーブルは必要なくなりますね。 ■ 余談 Edge Runtime が気になって 公式ドキュメント を読んでいると、Next.js v15.2(canary)から実験的に Node のランタイムの仕様を実験的に行っているそうです。

2025/03/08

VPS に Express 環境を構築する

Next.js と Node.js を使ってリアルタイムチャットアプリを開発しようと思い、成り行きで環境構築作業を行ったので備忘録としてメモしておきます。今回は OS(ubuntu)を再インストールして、まっさらの状態から作業を行います。 まずは Express 用のユーザーを作成。 sudo adduser node 作成したユーザーに sudo 権限を付与。 sudo gpasswd -a node sudo node ユーザーに切り替え。 sudo su node 公式ドキュメント を参考に nvm をインストール。 // nvm のインストール curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash // 環境変数を設定 export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm 最新の Node(LTS 版)をインストール。 nvm install --lts Node の確認。 node -v // 出力結果:v22.14.0 Express を常駐化させたいので pm2 というパッケージをインストール。 npm install pm2 -g 簡単な Express アプリの作成。 const express = require('express') const app = express() app.get('/', function (req, res) { res.send('Hello World') }) app.get('/foobar', function (req, res) { res.send('foobar') }) app.listen(3000) pm2 を使って実行。 pm2 start server.js 試しに curl でリクエスを投げてみる。 curl http://localhost:3000/foobar // 出力結果:foobar 元々は Express をリクエストの受け口にしようと思っていたのですが、色々調べてみたところ nginx をリバプロにしてローカルの Exprees にリクエストするのがお勧めだそうです。まぁ、そちらの方が運用が楽ですよね。(特に証明書の更新作業...)なので急遽 nginx をインストールします。 sudo apt install -y nginx 次にバーチャルホストの設定です。とりあえず海外のテックブログの内容を参考に設定しました。正直完璧には理解できていません...。今後作業を進めていくうちに分かってくるでしょう。 server { listen 80; server_name xxx.com; // VPS プロバイダーから付与されたドメインを設定 location / { proxy_pass http://localhost:3000; // ここでローカルの Express と紐づける proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } 設定後は念の為シンタックスチェック。 nginx -t // nginx: the configuration file /etc/nginx/nginx.conf syntax is ok // nginx: configuration file /etc/nginx/nginx.conf test is successful バーチャルホストの有効化。sites_enabled にシンボリックリンクを作成。 sudo ln -s /etc/nginx/sites-available/xxx.com.conf /etc/nginx/sites-enabled/ nginx の再起動と確認。 sudo systemctl restart nginx sudo systemctl status nginx node@t:/home/ubuntu$ sudo systemctl status nginx Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled) Active: active (running) since Sat 2025-03-08 21:40:25 JST; 10s ago Docs: man:nginx(8) ..... この時点で 80 ポート(HTTP)でのアクセスは可能になります。スクショはないですが、ちゃんとレスポンスが返ってきました。つまり、リバプロ(nginx)経由で Express にアクセスできたということです。レスポンスが返ってこなかった場合はポートが開いているか確認してみてください。また、VPS 側にパケットフィルタが設定されている場合があるので両方チェックすることをお勧めします。 続いて certbot を使って SSL 対応をします。まずはインストール。※ python3-certbot-nginx というパッケージもインストールしないと動きませんでした。 sudo apt install -y certbot python3-certbot-nginx 証明書の取得 & 設定。たったこの1行だけで全てやってくれます。ありがとう Let's Encrypt。 sudo certbot --nginx -d xxx.com 証明書の発行 & 設定に成功すると SSL が有効化され HTTPS でのアクセスが可能になります。とりあえずコレにて作業は完了です。ちなみに、Let's Encrypt で発行した証明書の有効期間は 90 日なので更新頻度が高めです。サーバを長く運用する場合は cron を使って自動更新するのがお勧めです。今回は保留。証明書の更新コマンドはこちら。 sudo certbot renew 久しぶりのインフラ周りの環境構築だったのでかなり調べながら作業を行いました。最近は Vercel なんかの無料ホスティング・デプロイメントサービスばかり使用するので完全に知識が薄れていっていますね〜。