REST原則と実装時の注意点 「REST WebAPI サービス 設計」 備忘録

RESTとは

REpresentational State Transferの略です。
分散型システムにおける設計原則群のことで、簡単に言うと設計ルールのことです。

REST制約の原則としては6種類あります。
※論文には7種類の記述がありますが、RESTの定義に直接的な関係はないため割愛いたします。

  • クライアント/サーバー
  • ステートレス
  • キャッシュ制御
  • 統一インターフェイス
  • 階層化システム
  • コードオンデマンド

REST制約

クライアント/サーバー

画面とデータという形で責務を分離させ、拡張性の向上と単純化を行います。

ステートレス

クライアントからサーバーへの要望は、その要望が満たされるために必要なすべての情報を含めるようにします。
サーバーはリクエストのみでその要望を理解できるということです。
サーバーセッションは使わず、クライアントで状態を保持し、リクエストの際にその状態をすべて含めてリクエストします。

キャッシュ制御

クライアント側でレスポンスをキャッシュできるようにすることを指します。
適切なキャッシュはUXの向上、リソース効率の向上、拡張性の向上が見込めます。

統一インターフェイス

あらかじめ定義・共有された方法でやり取りするということです。

リソースの特定

URIを用いてサーバーのデータを特定することを指します。
※「latest」や「famous」といった抽象的なものも含みます

表現を用いたリソース操作

レスポンスやPOSTデータといったリソースの断面を「表現」とし、それを利用してサーバー上のデータを操作することを指します。
また、認証情報などの追加情報も含めて考えます。
cache-controlでキャッシュの有無を明示しましょう。

自己記述メッセージ

メッセージの内容が何なのかがヘッダーに記述されていることを指します。
例: Content-Type

アプリケーション状態エンジンとしてのハイパーメディア(HATEOAS)

レスポンスの現状を踏まえ、関連のハイパーリンクが含まれることを指します。
※HATEOASはHypermedia as the Engine of Application Stateの略。

そのリンクを辿ることが、アプリケーションの状態遷移となります。
例えばECサイトで注文取消しの可否を判定するのに、「注文取消し用のURI」の情報がレスポンスに含まれているかどうかで判断するというのが挙げられます。

階層化システム

多層アーキテクチャ構成のことを指します。
責務を分けて独立させ役割を明確にすることで、進化と再利用の促進ができます。

コードオンデマンド

プログラムコードをサーバからダウンロードしてクライアント側で実行できるようにすることを指します。
サーバー主導で新規機能を追加することが可能になります。

REST API 成熟度レベル

REST の思想にどのくらい準拠できているかを表す指標のことです。
LEVEL 0~LEVEL 3があります。
LEVEL 2を満たしているシステムが多いです。

LEVEL 概要
LEVEL 0 HTTPを使用している状態。
LEVEL 1 リソースの概念が導入されている状態。
LEVEL 2 HTTPメソッドが適切に使用されている状態。
LEVEL 3 HATEOASの概念を導入されている状態。

REST API 作成時のhow to

URI設計

短く入力しやすく人間が読んで理解できるようにする

重複せず、シンプルで覚えやすい、人間が読んで理解できるものにします。

https://api.example.com/api/service/fds/search // ×:apiの重複。serviceは不要。fdsが何を指すかわからない。
https://example.com/foods/search // 〇

すべて小文字で、単語はハイフンでつなげる

小文字でルール化することで統一しやすくなります。
単語はハイフンでつなぐよう統一します。

https://example.com/famous_foods // ×
https://example.com/famous-foods // 〇

// さらに良いのは↓
https://example.com/foods/famous

可算名詞は複数形で

リソースの集合体となるため複数形で記述しましょう。

エンコードしなくて良い文字で

エンコードされてしまうと上記の「人間が読んで理解できるものにする」というものに反することになります。
ですので基本はエンコードしなくて良い英語で記述します。

サーバーのアーキテクチャの内容を反映させない

拡張子や使用しているWebサーバーの名称などをURIに使用しないようにしましょう。
悪意を持った利用者にサーバー側の情報を渡すことはセキュリティ上良くありません。
また、URIを読んで理解する際に不要な情報です。
そういった情報は含めないようにしましょう。

クエリパラメータとパスパラメータの使い分け

クエリパラメータを使うのは省略可能な場合です。
例えば複数項目での検索機能はクエリパラメータが向いています。

パスパラメータを使うのは一意のリソースであること、省略できない場合です。
例えばユーザーの詳細画面はパスパラメータが向いています。

HTTPメソッド

HTTPメソッドは、URIによって行われる処理によって使い分けを行います。

メソッド 対応する処理
GET リソースの取得
POST リソースの新規作成
PUT リソース全体の置き換え
DELETE 指定したリソースの削除
PATCH リソースの部分的変更
HEAD レスポンスヘッダのみ取得
OPTIONS リクエスト可能なHTTPメソッドの問い合わせ
CONNECT プロキシサーバに対し目的のWebサーバーへのトンネルを確立するよう依頼する
※接続先がHTTPSの場合に用いる
TRACE リクエスト内容をそのままクライアントに送り返す
デバッグなどに用いる

同じURIでもHTTPメソッドを変えることで処理を変えることができます。

URI設計とそれに対応するHTTPメソッドの例

ここでは実際にmovieをリソースとしてCRUD処理のURI設計とHTTPメソッドの割り当てを行ってみます。

処理 CRUD URI HTTPメソッド
アップロードする CREATE https://example.com/movies POST
movieの一覧を取得する READ https://example.com/movies GET
有名なmovieの一覧を取得する READ https://example.com/movies/famous GET
再生する(再生ページに移動する) READ https://example.com/movies/1 GET
更新する UPDATE https://example.com/movies/1 PUT
削除する DELETE https://example.com/movies/1 DELETE
特定の制作会社のmovieを削除する DELETE https://example.com/companies/1/movies DELETE

ステータスコード

ステータスコードは、処理結果の概要を通知するものです。
下記では大まかな分類と大まかな説明をします。それぞれ細かなステータスコードこちらで確認してください。

ステータスコード 説明
100番台 情報
200番台 成功
300番台 リダイレクト
400番台 クライアント側のエラー
500番台 サーバー側のエラー

レスポンスデータ構造について

必要なデータだけを返す

レスポンスボディには必要な情報のみ返しましょう。
ステータスコードのようにレスポンスヘッダーに含まれるような内容は、重複を避けるためにもレスポンスボディには記述しないようにしましょう。

オブジェクトはネストせず階層を浅くする

構造が複雑になるとデータ容量の増加と可読性の低下を引き起こします。
可能な限りフラットにしましょう。

ページネーションのための情報を返す

HATEOASのようにリンクを渡す方法を採用すると、同時刻でほかのユーザーがリソースを追加・削除していた場合に、 同じ情報が表示されたり表示したい情報が消えていたりする不都合があります。
それを解決するためには次ページが存在することを示すフラグと次ページはどこから表示すればよいかキーとなるものを用いる方法を使います。

日付はRFC3339形式を使う

RFC3339 2024-01-06T17:00:00+09:00

大きな数字は文字列で扱う

通常の整数は32bit整数で64bit整数は処理しきれないので、大きな数字は文字列で扱いましょう。

エラーの扱い

エラー詳細の扱い

ステータスコードのみだとどういったエラーなのか分からないため、エラーの詳細はレスポンスボディに入れましょう。
ですが、詳細に伝えすぎるとセキュリティ面で脅威を生む可能性があるので、概要のみ渡すようにしましょう。

エラー時にHTMLを渡さないようにする

渡す情報はXMLJSONなど定まった形式で返すようにしましょう。

サービスを閉じている際のエラー表現

ステータスコードは503でRetry-Afterにシステム再開日時を設定し、いつ再開するかを提示してあげるようにしましょう。

レートリミット(大量アクセス対策)

短期間の大量アクセスからサーバーを守る手段として使用します。
時間あたりのアクセス数を制御することで、悪意のあるボットや開発時に紛れ込んだ予期せぬ大量アクセスの対策になります。

ただ、一時的に大量アクセスを許容するような場合は一時的にレートリミットを緩和する必要があることもあります。

キャッシュ制御

キャッシュ制御に利用するヘッダーは有効期限による制御と検証による制御があります。

有効期限による制御

  1. Expires ヘッダー

  2. 利用期限を指定

  3. 過去日の指定で「有効期限切れ」を表現可能
  4. Cache-Control が指定されている場合は無視される

  5. Cache-Control と Date

  6. キャッシュの可否と期限を指定可能
可否の設定値 説明
public 通信経路上のどこからでも保存可能
private クライアント端末のみ保存可能
no-cache 有効性の確認を行ったうえで保存可能
クライアント側で保存します
no-store 保存不可
クライアント側で保存されないため常に通信が発生します
期限の設定値 説明
max-age=<秒> Dateヘッダーから何秒後に無効になるかを設定します

検証による制御

Last-Modified + ETag

  • Last-Modifiedヘッダーにリソースの最終更新日時を指定します
  • ETagヘッダーに特定バージョンを示す文字列を指定します(コンテンツハッシュ、バージョン番号、最終更新日時のハッシュなど)

キャッシュ単位

  • Varyヘッダーで他ヘッダー名を指定し、そのヘッダーごとにキャッシュします

セキュリティ

XSS

クロスサイトスクリプティング
悪意のあるユーザーが正規サイトに不正なスクリプトを挿入し、ユーザー情報の不正に取得・操作をすること。

対策

レスポンスヘッダーの追加
  • X-XSS-Protection : "1"でXSSフィルタリングを有効化する
  • X-Frame-Options : "DENY"でframeタグ呼び出しを拒否する

CSRF

クロスサイトリクエストフォージリ
Webアプリケーションのセッション管理の脆弱性を利用した攻撃方法で、Webサーバーに不正なリクエストを送信します。

対策

アクセス元を制限する
  • X-API-Key : システム単位で実行可否判断を行う
  • Authentication : ユーザー単位で実行可否判断を行う
    攻撃者から推測されにくいトークンの発行と照合処理を実装する

    *X-CSRF-TOKEN : トークンを使って実行可否判断を行う

JSON Web Token(JWT)

JWTとは

読み:ジョット
RFC 7519で標準化されています。
JSON形式で表したURL-safeなデータで、署名を用いてデータが改ざんされているかをチェックするために使われます。
認証結果をクライアントサイドで保持するような場面で利用されます。

構造はbase64UrlEncoeした、「ヘッダー」「ペイロード」「署名」を「.(ピリオド)」で結合した形式になっています。

部分 説明
ヘッダー 署名に用いるアルゴリズムなどを定義します。
ペイロード 保存するデータを格納します。発行・利用システムの識別子や有効期限などを指定します。
署名 改ざん防止に用いる署名部分です。ヘッダーのalgに指定したアルゴリズムや、アルゴリズムに合わせた鍵を指定します。

JWTにおけるセキュリティについて

クライアント側で内容の確認と編集ができるため、サーバー側での検証が不十分であれば改ざんされたデータを使用して不正アクセスなどを可能にしてしまいます。

対策

  • ヘッダーのalgにnone以外を指定し、署名を暗号化する
  • ペイロード部分のaudに想定する利用者を指定して受信時に検証を行う

WebアプリのDocker化についての要点

はじめに

言語関係なくWeb開発環境のDocker化で必要な事項についてまとめていきたいと思います。

Web開発においてはコンテナを単体で動かすより、複数のコンテナを連動させながら動かすことのほうが多いため、 オーケストレーションツール(docker-compose)についてもまとめていきます。

Dockerfile作成

Dockerを用いた独自の仮想環境を構築するためにはDockerfileが必要不可欠です。
作成するうえで必要な内容について記述していきます。

Dockerfile作成に必要な内容としては下記があります。

  • DSL: ドメイン固有言語 / ドメイン特化言語としてDockerfileのコマンドについて説明します
  • セキュリティ: Dockerfileを記述する上でのセキュリティやコンテナ技術のセキュリティについて説明します
  • ベストプラクティス: Dockerでの環境運用を行う上で良しとされている方法について説明します

生成コマンド

2023/12下旬現在ベータ版ではありますが、下記コマンド実行を行うことで 必要なファイルの生成ができます。

・Dockerfile
・.dockerignore
・compose.yaml
・README.Docker.md

docker init [OPTIONS]
# OPTIONSにはinit プラグインのバージョンを表示する--versionがあります。


コマンドを打つと言語を選ぶような質問が返ってくるためその中から選択を行います。

選択肢としては下記があります。
- Golang
- Node.js
- Python
- PHP(Apacheを利用した)
- Rust
- ASP.NET
- その他
現在は実稼働環境でそのファイルを利用することは非推奨とされているので、
本番環境では利用しないようにしてください。

逐一ドキュメントで確認することをお勧めします。

docs.docker.com

DSL

Dockerfileには非推奨のもの含め18のコマンドが存在します。
Dockerfileリファレンス 日本語
ちなみに、DSLとはドメイン固有言語 / ドメイン特化言語という意味合いであり、特段Dockerfileで記述するコマンドのことのみを指す言葉ではありません。

コマンドまとめ

コマンドのリンクからは日本語のドキュメントに遷移できます。
構文はリンク先からご確認ください。

コマンド 説明
FROM 以降の命令で使うベースイメージを指定します。
正しいDockerfileはFROMから開始する必要があります。Dockerhubにアップロードされている公式のイメージファイルを使うのが一般的です。
CMD コンテナ起動時に記述したコマンドを実行し、フォアグラウンドで実行されている間が生存期間になります。
CMD の主な目的は、コンテナ実行時のデフォルト(初期設定)を指定するためです 。
実行ファイルを含まない場合は、 ENTRYPOINT 命令の指定が必要です。
ENV コンテナ内で使用する環境変数を定義します。
キーと値で設定します。
途中で書き換えも可能です。
維持すると悪影響を及ぼすこともあるため、
不都合がある際はRUNコマンドで値の指定を行います。
WORKDIR Dockerfileでコマンド実行する際に基準となるディレクトリを設定します。
デフォルトは'/'です。最悪の場合既存のディレクトリを上書きする可能性があるため必ず指定します。
COPY ホストからコンテナ内にファイルやディレクトリをコピーします。
ホスト側のディレクトリはdocker buildで指定したディレクトリとなります。
コンテナ側はWORKDIRで指定したディレクトリとなります。
RUN コンテナ内でコマンドを実行します。Linuxだとデフォルトはsh。
&&でつなぐことでレイヤーが減るためそうしない場合より速くなります。
パッケージのインストールや権限、ユーザー設定などを行います。
不要なパッケージのキャッシュを削除するコマンドも各コマンドと組み合わせて使いましょう。
rm -rf /var/lib/apt/lists/* && rm -rf /var/cache/yum/*
USER コンテナで実行するユーザーを設定します。
rootがデフォルトとなっているため、セキュリティの観点からも必ず指定をしてください。
EXPOSE コンテナ内で実行するポートの指定を行います。
ホスト側へ公開する場合は-Pでポート番号を指定する必要があります。
なお、docker-composeで起動する場合はportsによる指定で開放します。
VOLUME Data Volumeを作成するためのコマンドです。
ボリュームとはデータを永続化させるものです。
基本的にはログのような更新頻度の激しいファイルで使用します。
ARG ビルド時に変数を使用するためのコマンドです。
デフォルト値を設定することができます。
設定以後、その変数を利用することができます。
構築ステージが終了するまで利用できます。
変数を渡すには、構築時に docker build コマンドで --build-arg <変数名>=<値> を指定します。
指定しなかった場合は警告されます。
ADD 指定したパスやURLのファイルをコンテナ内にコピーするコマンド。
認識できる圧縮形式( 無圧縮identity 、 gzip 、 gzip2、xz )の tar アーカイブの場合はディレクトリとして展開されます。
ENTRYPOINT 指定されたコマンドを実行します。
CMDとは異なり、docker run時に引数を指定することができます。
また、実行ファイル形式で処理するように設定することもできます。
LABEL キーとバリュー構成されるメタデータを付与することができます。
docker imageでは複数のLABELを持つことができ、1行で複数設定することができます。
ONBUILD 次のビルド時に利用するコマンドを設定することができます。
親イメージでONBUILDを設定していると、それをFROMに指定した子イメージをビルドした際、FROM直後にONBUILDで指定したコマンドが動作します。
STOPSIGNAL コンテナが終了するために送信するシステムコールシグナルを設定します。
カーネルの syscall 表と一致します。
HEALTHCHECK コンテナが停止していないかどうか確認する、もしくは親イメージからのHEALTHCHECKを無効化するためのコマンドです。
サーバのプロセスが実行中の無限ループ詰まりや新しい接続を処理できなかったりするような問題を検出することができます。
使用するとhealth statusが追加されます。
health statusの状態はstarting, healthy, unhealthyの3つがあります。
SHELL コマンドで利用するシェル形式において用いられるデフォルトのシェルを上書き設定するために利用します。
MAINTAINER(非推奨) ビルドするイメージのAuthorフィールドを設定できます。
ただこちらは削除予定のため覚える必要はないかと思います。
今後はLABELで設定します。

セキュリティ

ユーザー指定する

ユーザー指定を行わない場合、コンテナの実行はrootユーザーで行われます。
不正アクセスやコンテナ漏洩のリスクを下げるためにもUSERの設定を行いましょう。

信頼できるイメージを使用する

Docker Hubの画面上にバッジが幼児されている下記から選ぶのが比較的安全です。

  1. Docker Official Image
    Docker Hub上で最も信頼できるイメージ群です。
    ベースOSやプログラミング言語ミドルウェアなどのイメージが含まれます。

  2. Verified Publisher
    Dockerによって検証された発行者が作成したイメージ群です。
    各種エージェントプログラムが含まれます。
    例としてaws-cliのようなツールがあります。

  3. Sponsored OSS
    DockerがスポンサードしているOSSプロジェクトによって公開されているイメージ群です。
    Dockerが検証を行っているため安心して利用できます。

.dockerignoreファイル

.envファイルのような機密情報を扱うファイルをコンテナ内に含めないようにしましょう。
記述は.gitignoreと同じくファイルパスを記述します。

ソフトウェアの脆弱性

コンテナイメージ作成後はイミュータブルなものとして扱われるため、定期的に脆弱性の診断を行い、
ローカル環境で脆弱性を解消し、再ビルドと再デプロイを行う必要があります。

latestタグを使用しない

オートスケール時などコンテナが作成・再作成されるタイミングを完全に把握することは難しいため、
latestタグのイメージを利用している場合は、その各コンテナでアプリケーションバージョンが異なってしまう場合があります。
その際、後方互換性がないバージョンだと障害に繋がってしまう可能性があります。
また、Docker Hubなどのレジストリでは同じタグに再プッシュが可能なため、アカウントの乗っ取りがあった場合に悪意のあるイメージをプッシュされる可能性があります。
これを防ぐためにセマンティックバージョニングを用いたバージョン設定やハッシュ値を用いたバージョン設定をすることが推奨されています。

ベストプラクティス

扱いやすいイメージを作るための「すべき」「したほうが良い」という項目について取り上げます。
ただ、「しなければならない」ではないため、場合によって使い分けていきましょう。

1プロセス1コンテナ

各コンテナはただ1つだけの用途を持つべき

ベストプラクティスにはそのように記載されています。
これはコンテナを簡単にスケールさせたり再利用性を高めたりするため、アプリケーションを複数のコンテナに切り離すものです。
必須ではないですが、水平スケールや再利用性を高めたい場合は採用しましょう。

軽量なイメージを作成する

Docker Imageは軽量であればあるほど良いとされています。
そのため不要なものは入れず、軽量なものを使っていくという方針を取ります。
その中で有用な方法をまとめます。

alpineを使用する

Linuxディストリビューション環境を利用する場合はAlpine Linuxを使用することで軽量化することが望めます。
例えばLinuxディストリビューションの中で人気のあるUbuntuと比較した際、
Ubuntuイメージは約80MBですが、Alpine Linuxは約7MBほどです。

distrolessという選択肢

distrolessとはGoogleが提供している軽量なコンテナイメージ群の名称です。
動作させたいアプリケーションとその実行に必要な依存関係のみを入れて使うことを前提としているため、 最小限のファイルしか存在せず、シェルすらも含まれていません。
その結果、軽量化とセキュリティの向上が期待できます。

レイヤの最小化

RUN、COPY、ADD命令はイメージ全体の実容量を増やします。
ほかの命令は構築時の一時的な中間イメージで使われますが、イメージの全体の容量には影響を与えません。
下記のように1コマンドごとに1つのRUNを準備するとその分レイヤが増えていくことになります。

RUN apt-get update
RUN apt-get install git ~

それを && でつないで(改行時は「\」)、なるべくひとつのレイヤにまとめます。

RUN apt-get update && \
    apt-get install git ~

ただ、後述のマルチステージビルドを使って最終的に利用するイメージの容量やレイヤ数を減らすのも1つの手となります。

最小限の構成にする

不要なパッケージは入れない

環境構築に際して必要なもののみ入れるようにしましょう。
「あったほうが良いかも」程度のパッケージのインストールは行いません。
そういったパッケージが増えることで依存関係の複雑さが発生し、イメージの容量が増えることに繋がります。

Debian環境ではapt-getコマンドを利用する場合は--no-install-recommendsオプションを付けたイメージ構築を行うことで、 関係する推奨パッケージの自動インストールをしないようになります。

マルチステージビルド

ひとつのDockerfile内で複数のFROMを記述することで、段階的にイメージのビルドができる機能です。
セキュリティやレイヤ数の観点からも利用が推奨されています。
Golangのようなビルドでバイナリを出力するような言語でマルチステージビルドを行うことで軽量化が見込めます。
開発・検証環境ではソースコードや依存関係、開発ツール群を含め、本番環境ではバイナリのみといった形を構築することも可能です。

下記、docker docsからの引用です。

# syntax=docker/dockerfile:1
FROM golang:1.16 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go    ./
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app ./
CMD ["./app"]

最初のFROM golang:1.16 AS builderで設定したエイリアスを使い、
下部のCOPY --from=builder /go/src/github.com/alexellis/href-counter/app ./の--from=builderで利用しています。

オーケストレーションツール導入

オーケストレーションツールとは

複数のコンテナを効率的に管理するためのツールです。
コンテナ間通信(ネットワーク)やオートスケールといった機能を備えています。 例としてdocker-composeKubernetesがあります。

利便性の高さからWeb開発ではDockerと抱き合わせで利用されることが多いです。

下記ではdocker-composeについて記載していきます。

compose.ymlファイル

docker-composeを用いたオーケストレーションを行うためにYAML形式(compose.yml)のファイルを用います。
ここではよく使うものに絞って記述いたします。それ以外の設定についてはこちらからご確認ください。

記述方法

キー 説明
services 起動するコンテナの定義を行います。
サービス名は任意の名前を設定することができます。
volumes ボリュームを作成するための記述です。
ここで定義したボリュームはサービス内のvolumesで利用することができます。
※ボリュームとはコンテナ内のデータを永続化するためのもので名前付きと匿名ボリュームがあります。
networks コンテナ間通信について独自のネットワーク設定を行うことができます。
networks以下のインテントでネットワーク名を記述し、さらにそれ以下にネットワークのドライバなどの設定を行います。

サービス以下のインデントで使用する設定はこちらです。

キー 説明
image イメージの指定を行います。
イメージが存在しない場合、Composeはpullを行います。
container_name コンテナ名を指定します。
コンテナ名の重複はできないため、指定した場合は複数コンテナにスケールできなくなります。
build ビルド時に適用するオプションを設定します。
ビルドするDockerfileのパスを指定したり、引数の設定を行ったり、ビルド関連の設定ができます。
volumes ボリュームのマウントを指定します。
コンテナとホストの間でデータ共有するために設定します。
environment 辞書形式で環境変数の設定を行うことができます。
ports ポートの設定を行うことができます。
ホスト側ポート番号:コンテナ側で設定を行い、公開用のポート番号を設定します。
depends_on サービス間の依存関係を設定します。
docker compose upを行った際にこの項目に記載があるコンテナを先に起動します。
docker compose stopの際は依存するサービスの前に、設定したサービスを停止します。
command コマンドを記載すると、Dockerfileに記載されているコマンドを上書きしてコマンド実行します。
healthcheck コンテナが正常かどうか判断するために実行するコマンドを記載します。
確認の間隔や試行回数など細かく設定することが可能です。

それ以外にも便利な設定が多く存在するのでリファレンスで確認してみてください。

docs.docker.jp

Laravel Breezeインストールで追加されるファイルがLarastanに引っかかる

はじめに

会社の自社製品を作ろうとなり、その土台を任されたので土台をLaravelにしました。

開発体験をよく出来たらと、そこに認証機能のBreezeと静的解析ツールのLarastanを導入しましたが、そこでしょっぱなからエラーに遭遇しました。

日本語記事も出てこなかったため、書いておきます。
結論Illuminate\Contracts\Auth\MustVerifyEmailというインターフェイスをUserモデルで実装しろっていう話ですね。

環境

Laravel v10.28.0 Laravel Breeze v1.26.1 larastan v2.6.4

エラー文

App/Http/Controllers/Auth/VerifyEmailController.php::23行目
Parameter #1 $user of class Illuminate\Auth\Events\Verified constructor expects Illuminate\Contracts\Auth\MustVerifyEmail, App\Models\User|null given.    

解決策

App/Models/User.php

+ use Illuminate\Contracts\Auth\MustVerifyEmail;

- class User extends Authenticatable
+ class User extends Authenticatable implements MustVerifyEmail

あとがき

メールでのアカウント確認が不要ならそのファイルと関連の記述を削除すればよいかと思いますね。

ほかにもBreezeインストールで実装されたcomponentsたちが使われていないというようなエラーとかも出ているので、 解決したら追記します。

よく使うLinux コマンドまとめ

はじめに

Web開発を行う上でLinuxは避けては通れないものです。

知識の定着のため、自分なりの言葉でよく使うコマンドを一覧化しておきます。

一覧

[]で囲んているものは任意です。 囲んでいないものは必須の内容です。

コマンド 構文 説明
cd cd パス Change Directoryの略。
ターミナル上での現在地(ディレクトリ)を変更する。
pwd pwd [オプション]... Print name of Working/(current) Directoryの略。
ターミナル上での現在地(ディレクトリ)がどこなのかを出力する。
ls ls [オプション]... ファイル名やディレクトリ名... List Segmentsの略。
ディレクトリやファイルの一覧を出力する。
オプションを付けない場合は隠しファイルは出力されない。
よく使うオプションは
-a:すべてのファイル、ディレクトリを表示する。
-l:ディレクトリやファイルに設定されている権限や所有者などが表示される。
-t:新しい順にソートして出力する。-lと併用することが多い。
mkdir mkdir [オプション]... ディレクトリ名... Make Directoriesの略。
現在のディレクトリにディレクトリを作成する。
rmdir rmdir [オプション]... ディレクトリ名 Remove Empty Directoriesの略。
空のディレクトリを削除する。
cat cat [オプション]... ファイル... Concatenate(連結するを意味する語)の略。
ファイルの中身と標準出力を連結する=ファイルの中身を出力するということ。
less less [オプション] ファイル... moreコマンドの対義をなすもの。
moreとほとんど同じくファイルの中身を出力するのだが、moreはファイル終了後にEnterもしくはSpaceキーを押すことで自動的にプロダクトに戻る。lessはqキーを押さないと戻らない。
Spaceキーを押すことで次のページを開くようにファイルの中身を次の行から始まるように表示してくれる。
tail tail [オプション]... ファイル... ファイルの末尾(しっぽ)を10行(デフォルト)表示する。
複数ファイルの末尾を表示したい場合は半角スペースでつなげてファイル名を記述する。
-n:その後に記述した行数表示してくれる。
touch touch [オプション]... ファイル... ファイルのタイムスタンプ(アクセス日時、更新日時)を更新するコマンド
存在しないファイル名を指定すると空ファイルを作成する。
からファイル作成のために使うことが多い。
rm rm [オプション]… ファイル… Remove Directory Entriesの意。
ディレクトリ内の要素を削除する。
-r: ディレクトリを再起的に削除する。
-f: 存在しないファイルを無視する。確認もしない。
-i: 削除前に確認する。
mv mv [オプション]… パス(移動前)… パス(移動先) moveの略。
ファイルやディレクトリを移動させる。
また、ファイルやディレクトリの名前を変更することもできる。
-b: 上書きするファイルのバックアップを取る。
-n: 移動先に同じ名前のものがある場合移動しない。
cp cp [オプション]... パス(コピー元)... パス(コピー先) copyの略。
ファイルやディレクトリをコピーする。
mvとは違い、存在しない名称を入力した時に新規ファイルなどが作られない。
-n: 存在するファイルを上書きしない。
-f: 強制的に上書きする。
-i: 上書きする前に確認する。
ln ln [オプション]... ファイル リンク名 make links between filesの意。
ファイルのリンクを作成する。
ハードリンク:存在するファイルを参照するリンクのこと。
シンボリックリンク:ファイルやディレクトリを参照するリンク。
-s: シンボリックリンクの生成
-f: 登録済みのリンクであっても上書き登録する。
-i: 登録済みリンクへ上書きする場合確認する。
find find [検索する場所] [判別式/演算子] ファイル名 ファイルやディレクトリを検索する。
-name: ファイル名での検索ができる。ワイルドカードが使用可能。
-atime: 任意の日数にアクセスされたファイルやディレクトリを検索する。
-mtime: 任意の日数に更新されたファイルやディレクトリを検索する。
-empty: ファイル容量が0のファイルやディレクトリを検索する。
-type f: ファイルを対象とする。
-type d: ディレクトリを対象とする。
grep grep [オプション]... 検索したいパターン [ファイル名] パターンにマッチしたラインを出力する。
-i: 大文字小文字の区別をしない。
-n :検索結果に行番号を表示する。
-r :ディレクトリ内も検索対象とする。
chmod chmod [オプション]... 権限 対象のファイル ファイルやディレクトリの権限を設定する。
権限は3桁の数字もしくは記号で設定する。
数字で設定する場合、1桁目:管理者ユーザー 2桁目:所有者/所有グループ 3桁目:その他 となっている。
各最大値は7。読み取り(r):4、書き込み(w):2、実行(x):1の和。
記号で設定する場合、所有者:u、グループ:g、その他のユーザー:o、すべてのユーザー:aに対し、上記r、w、xを=で繋ぐ。
-R :再帰的に設定する。
chown chown [オプション]... ユーザー[:グループ] 対象ファイル ファイルの所有者や所有グループを変更する。
-R :再帰的に設定する。
ps ps [オプション]... 現在のプロセス一覧を出力する。
-A: すべてのプロセスを出力する。
a: 端末操作のプロセスを他ユーザーのものも含めて表示する。
u: ユーザー名や開始時刻、CPUやメモリの使用率などを表示する。
x: 端末操作以外の全てのプロセスも表示する。
kill kill [オプション]... プロセスID プロセスを終了させる。
-l: シグナルの一覧を表示する。
-[シグナルID]: シグナルの種類を指定する。

TypeScriptにおける型ガードまとめ

はじめに

TypeScriptで開発を行う上で型の判定を行う場合が多々あります。 自分自身たまにどうすればよいか迷子になってしまうこともあるため備忘録としてまとめておきます。

概要

  • プリミティブ型の判定:typeof
  • プロパティ・メソッドの存在確認による判定:in
  • クラスのインスタンスかどうかの確認による判定:instanceof
  • オブジェクトに判別可能なプロパティを設けてそれで判断する:Descriminated Unions

typeof

プリミティブ型(string, number, boolean, undefined, null, symbol, bigint)を文字列で返却してくれるため、それを同値演算子(===)もしくは非同値演算子(!==)を使って判定を行います。

// a,b どちらかがstringなら文字列結合、numberなら足し算を行う
const add = (a: string | number, b: string | number) => {
  if (typeof a === "string" || typeof b === "string") { // ここの部分
    return a.toString() + b.toString();
  }
  return a + b;
};

in 演算子

〇〇がオブジェクトに存在するか確認することで型ガードを行う方法です。

type Foo = {
  name: string;
  age: number;
}

type Bar = {
  name: string;
  email: string;
}

type FooBar = Foo & Bar;

const Baz: FooBar = (args: FooBar) => {
  console.log(args.name);
  if ("age" in args) { // argsにageプロパティがある場合
    console.log(args.age); 
  }
};

instanceof 演算子

指定したクラスのインスタンスであるかどうかを判定させ、型ガードを行う方法です。 interfaceで定義した型の情報はJavaScriptコンパイルされないため、この方法を使うことはできません。

class Foo {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

class Bar {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}
const Fuga = new Foo("foo", 25);
const Hoge = new Bar("bar", "test@example.com");

type Baz = Foo | Bar;

const whatIsInstance = (arg: Baz) => {
  console.log(arg.name);
  if (arg instanceof Foo) console.log(arg.age);
  if (arg instanceof Bar) console.log(arg.email);
};

Descriminated Unions

interface Bird {
  type: "Bird"; // ここが該当の箇所
  flyingSpeed: number;
}

interface Dog {
  type: "Dog"; // ここが該当の箇所
  runningSpeed: number;
]

type Animal = Bird | Dog;

const getSpeed = (animal: Animal) => {
  switch (animal.type) {
    case: "Bird":
      return animal.flyingSpeed;
    case: "Dog":
      return animal.runningSpeed;
  }
}

【TypeScript】メソッドのthisの扱い次第ではTypeScriptでエラー検知できない

メソッドでthisを用いる際にTypeScriptではエラー検知できない場合がある

コンパイルが通るが、実行時にエラーになるようなパターンが起こり得ます。
それについて記述していきます。

再現方法

class Car {
  name: string;
  gas: number;

  constructor(name: string, gas: number = 0) {
    this.name = name;
  }

  addGas(amount: number) {
    this.gas += amount;
  }
}

const car = new Car('PRIUS', 20);
car.addGas(10); 

const copyCar = {addGas: car.addGas};
copyCar.addGas(20); 

ここでは特にエラーを表す赤い波線は出ていません。
そのためコンパイルが通ります。 エラーを表す赤い波線は出ていません。

addGasメソッドを呼び出してみます。

carでは燃料追加することができましたが、copyCarではNaNと表示されてしましました。
carでは燃料追加することができましたが、copyCarではNaNと表示されてしましました。

copyCarにはgasプロパティが定義されていないからです。

メソッドでthisを用いている場合、
メソッドの前にあるオブジェクト内のプロパティやメソッドを指します。

car.addGas()だとcarオブジェクトのgasプロパティを参照し、
copyCar.addGas()だとcopyCarのgasプロパティを参照しようとします。

もちろん、copyCarにはgasプロパティは存在しない(undefined)ため
gasを追加することができずNaNになります。

対処方法

きちんとTypeScriptで検知するためには
メソッドの第1引数(※必ず第1引数にする)にthis: クラス名を入れます。

今回の例では下記のようにします。

  addGas(this: Car, amount: number) {
    this.gas += amount;
    console.log(this.gas);
  }

そうすることできちんとエラーが出現します。

copyCar.addCopyにエラーがあることを示す、赤い波線が出ています。

The 'this' context of type '{ addGas: (this: Car, amount: number) => void; }' is not assignable to method's 'this' of type 'Car'.
  Type '{ addGas: (this: Car, amount: number) => void; }' is missing the following properties from type 'Car': name, gas

type '{ addGas: (this: Car, amount: number) => void; }' の 'this' コンテキストは、type 'Car' のメソッドの 'this' に代入できません。
  タイプ '{ addGas: (this: Car, amount: number) => void; }' には、タイプ 'Car' の以下のプロパティがありません: name, gas

このことからわかるように、thisに対してCarクラスの型であることを明示してあげることでエラーを表示させることができるようになります。

Vim基本コマンドまとめ

Vim基本コマンドについて

はじめに

まとめとしてよく使うコマンドを記述しております。 ただ、

キーなのか、入力するものなのかを分けるため下記ルールを設定しています。 ※当然のことながら下記の括弧は入力しなくて良いです [ ]はキーを表す {{ }}は入力値を表す

モード変更

動作 キー、コマンド
インサートモード [ i ]
次の文字からインサートモード [ a ]
選択行下に空白を入れ、インサートモード [ o ]
選択行に空白を入れ、インサートモード [ O ]
ノーマルモード [ esc ]
コマンドモード [ : ] or [ / ]
ビジュアルモード [ v ]
演算子未解決モード
※実行範囲の指示が必要なモード
[ d ]

操作

移動

動作 キー、コマンド
上に移動 [ k ]
下に移動 [ j ]
左に移動 [ h ]
右の移動 [ l ]
ファイルの先頭へ移動 [ g ][ g ]
ファイルの最後尾へ移動 [ G ]
ウィンドウ内最後尾へ移動 [ L ]
ウィンドウ先頭へ移動 [ ^ ]
次の単語の先頭に移動 [ w ] or [ W ]
カーソル上の単語の先頭→前の単語に移動 [ b ] or [ B ]
前の単語の末尾に移動 [ g ][ e ] or [ g ][ E ]
指定した行数に移動 [ : ]{{ 行数 }}[Enter]
行末に移動 [ $ ]
先頭に移動 [ 0 ]
インデントの先頭に移動 [ ^ ]
段落ごとに上に移動 [ { ]
段落ごとに下に移動 [ } ]
セクションごとに上に移動 [ [ ][ [ ]
セクションごとに下に移動 [ ] ][ ] ]

コマンドモード

コマンド

入力で実行される

動作 コマンド
コピー [ y ]
一行コピー [ y ][ y ]
n行コピー {{ 数字 }}[ y ][ y ]
下にペースト [ p ]
現在行にペースト [ P ]
1文字削除 [ x ]
単語の切り取り [[ d ][ w ]
カーソルから末尾まで切り取り [ d ][ $ ]
1行切り取り [ d ][ d ]
n行の切り取り {{ 数字 }}[ d ][ d ]
元に戻す [ u ]
「元に戻す」を戻す [ Control ][ r ]

コマンド

Enterで実行する

動作 コマンド
保存 [ : ][ w ]
名前をつけて保存 [ : ][ w ][ Space ]{{ ファイル名 }}
保存せず編集終了 [ : ][ q ][ ! ]
保存して終了 [ : ][ w ][ q ]
ワード検索 [ / ]{{ 検索したい語句 }}
→次の結果に移動 [ n ]
→ひとつ前の結果に移動 [ N ]
一括置換 [ : ]{{ %s/検索値/変更値/g }}
確認を行いながら置換 [ : ]{{ %s/検索値/変更値/gc }}
右にインデントを移動 [ > ]
左にインデントを移動 [ < ]
コマンド実行 [ ! ]{{ コマンド }}
直前のコマンドを実行 [ ! ][ ! ]