2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【実践】FWなし! DBなし! 純粋なPHPで作る最小構成REST API

2
Posted at

はじめに

「フロントエンド(React/Vueなど)やアプリから呼び出す API を作りたいけれど、何から始めればいいかわからない」
「REST API について、多少わかってはいるけど、一人で作れるほどじゃない」
「Laravelなどのフレームワークや、データベースの設定で挫折した……」

そんな方でも大丈夫です。
API の基本を作るのに、フレームワーク(FW)もデータベース(DB)も
最初はまったく不要です!

この記事では、難しい座学や環境構築をなるべく省略し、
「純粋なPHPファイル(Vanilla PHP)だけで、動くREST APIを作る」 方法を解説します。

あなたが REST API の基本を理解する、最初の一助になれば幸いです。

最低限覚えること

実践に入る前に、以下の3つのルールだけ押さえておいてください。

  1. REST API はリソースを URL(より正確に言うと URI)で特定する
    • 例:/users/1
  2. REST API は HTTP メソッドで役割を示す
    • GET : クライアントがデータを取得したいときのメソッド。
    • POST : クライアントがデータを送信したいときのメソッド。
    • その他は省略します。
  3. REST API は JSON 形式のデータを返す
    • やり取りするデータの形式は、一般的に JSON が使われます。
      • JSON は、{"status": "success"} のようなテキスト形式です。

詳しく知りたい方は「REST API 設計」などで検索してみてください!

本編(実践)

では、API エンドポイントとなる api.php を作っていきましょう。

前提条件

  • php がインストールされていること(PHP 8.x 以降推奨)

コマンドプロンプトやターミナルで、以下を実行して確認してください。

php -v

PHP 8.4.5 … のように、PHP のバージョンが表示されればOKです!

1. API エンドポイントのベースの作成

以下のコードを、api.php として保存してください。

api.php
<?php
// レスポンスヘッダーの設定(JSON形式指定 & UTF-8指定)
header('Content-Type: application/json; charset=utf-8');

// リクエストメソッドの取得
$method = $_SERVER['REQUEST_METHOD'];

// HTTPメソッドによる処理の分岐
switch ($method) {
    // GET メソッドの場合
    case 'GET':
        handleGet();
        break;

    // POST メソッドの場合
    case 'POST':
        handlePost();
        break;

    // 許可していないメソッドの場合
    default:
        // レスポンスの作成
        $response = [
            'status' => 'error',
            'message' => '許可されていないメソッドです'
        ];

        // HTTP ステータスコードの設定(405 Method Not Allowed)
        http_response_code(405);

        // ヘッダーを設定
        header('Allow: GET, POST');

        // 配列をJSON文字列に変換して出力
        echo json_encode($response, JSON_UNESCAPED_UNICODE);

        break;
}

// ----------------------------------------------------
// GET 処理(データの取得)
// ----------------------------------------------------
function handleGet(): void
{
    $data = [
        'id' => 1,
        'name' => '田中太郎'
    ];

    // レスポンスの作成
    $response = [
        'status' => 'success',
        'message' => 'データを取得しました',
        'data' => $data
    ];

    // HTTP ステータスコードの設定(200 OK)
    http_response_code(200);

    // 配列をJSON文字列に変換して出力
    echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}

// ----------------------------------------------------
// POST 処理(データの登録)
// ----------------------------------------------------
function handlePost(): void
{
    // 送られてきた値を取得
    $rawInput = file_get_contents('php://input');
    $data = json_decode($rawInput, true);

    // レスポンスの作成
    $response = [
        'status' => 'success',
        'message' => 'データを登録しました',
        'data' => $data
    ];

    // HTTP ステータスコードの設定(201 Created)
    http_response_code(201);

    // 配列をJSON文字列に変換して出力
    echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}

ポイント解説

  • header('Content-Type: application/json')
    • クライアントに「JSON を返します」と伝えるための、必須のヘッダーです。
  • JSON_UNESCAPED_UNICODE
    • これを指定しないと、日本語などが \u3084 のような文字列になり、
      人間にはそのままでは読めなくなってしまいます(Unicodeエスケープ)。
  • JSON_PRETTY_PRINT
    • JSON 文字列を改行・インデント付きで整形し、
      見やすくするためのオプションです。
      本番環境では、レスポンスサイズ削減のため使わないことが多いです。

$_POST ではなく php://input である理由

上記コードでは、送られたデータを
$rawInput = file_get_contents('php://input'); として取得しています。

通常、HTMLフォームから送信されたデータは $_POST['name'] で取得できますが、
Content-Type: application/json で送られたデータは、$_POST には入ってきません。

そのため、file_get_contents('php://input') を使って、
送信された生のデータ(JSON 文字列)を直接読み込み、
json_decode() で PHP の配列に変換する必要があります。

2. 動作確認

PHPのビルトインサーバーを使えば、ApacheやNginxがなくてもその場で動かせます。

サーバーの立ち上げ

コマンドプロンプトかターミナルで、
作成した api.php があるディレクトリーに移動し、
以下を実行します。

php -S localhost:8000

これで http://localhost:8000/ でアクセスできるようになります。

確認方法

ブラウザーでの確認方法

ブラウザーを開き、F12を押してデベロッパーツールに移動し、Console タブに移動してください。
そして、以下のコードを貼り付けて実行してみてください。

※コンソールで実行する際は、CORS 制約に引っかかる可能性があるため、
 http://localhost:8000 を開いた状態で実行するか、拡張機能等をご利用ください。

GET のテスト
fetch('http://localhost:8000/api.php')
  .then(res => res.json())
  .then(data => console.log(data));

{status: 'success', message: 'データを取得しました', data: {…}} のような値が返ってきたら成功です。

POST のテスト
fetch('http://localhost:8000/api.php', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id: 1, name: '田中太郎' })
})
.then(res => res.json())
.then(data => console.log(data));

{status: 'success', message: 'データを登録しました', data: {…}} のような値が返ってきたら成功です。

Talend API Tester での確認方法

Talend API Tester という、Google Chrome 拡張があります。
こちらを Chrome ブラウザーに入れてテストすることができます。

GET のテスト
  • METHOD は 'GET' を選択
  • URL には http://localhost:8000/api.php を入力

の状態で Send を押します。
BODY に以下のような値が返ってきたら成功です。

{
    "status": "success",
    "message": "データを取得しました",
    "data": {
        "id": 1,
        "name": "田中太郎"
    }
}
POST のテスト
  • METHOD は 'POST' を選択
  • URL には http://localhost:8000/api.php を入力
  • HEADERS から Add header を押し、 Content-Type : application/json を入力
  • BODY には以下のように入力
{
    "id": 2,
    "name": "鈴木花子"
}

の状態で Send を押します。
BODY に以下のような値が返ってきたら成功です。

{
    "status": "success",
    "message": "データを登録しました",
    "data": {
        "id": 2,
        "name": "鈴木花子"
    }
}

3. API の改良

先ほど作ったファイルを少し拡張してみましょう。

  • GET API
    • URL(正確には URI)に応じたデータを返せるようにします。
  • POST API
    • バリデーションチェックを加えます。

GET メソッド処理の改良

先ほどの api.php 内の処理を改良していきましょう。

まずは、ファイル上部にコードを追加します。
以下のように変更してください。

api.php
// ...以上略...

// リクエストメソッドの取得
$method = $_SERVER['REQUEST_METHOD'];

// パス情報の取得
$pathInfo = $_SERVER['PATH_INFO'] ?? '';
$pathParts = explode('/', trim($pathInfo, '/'));
$id = isset($pathParts[0]) && $pathParts[0] !== '' ? (int)$pathParts[0] : null;

// HTTPメソッドによる処理の分岐
switch ($method) {
    // GET メソッドの場合
    case 'GET':
        handleGet($id);    // 引数を追加
        break;

    // ...以下略...

これにより、/api.php/2 のようにアクセスしたとき、
/2 部分が $id として取得できるようになりました。

PATH_INFO について

今回は PHP のビルトインサーバーで動かす前提のため PATH_INFO を利用していますが、
環境によっては利用できないことがあります。注意してください。

また、$_SERVER['REQUEST_URI'] からパースする方法もあります。

次に、handleGet() を以下のように変更します。

api.php
function handleGet(?int $id): void
{
    // 擬似的なデータテーブル
    $users = [
        1 => ['id' => 1, 'name' => '田中太郎', 'email' => 'taro@example.com'],
        2 => ['id' => 2, 'name' => '鈴木花子', 'email' => 'hanako@example.com'],
    ];

    // ID未指定の場合は一覧を返却 (/api.php)
    if ($id === null) {
        // レスポンスの作成
        $response = [
            'status' => 'success',
            'message' => 'ユーザーデータを取得しました',
            'data' => array_values($users)
        ];

        // HTTP ステータスコードの設定(200 OK)
        http_response_code(200);

        // 配列をJSON文字列に変換して出力
        echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
        return;
    }

    // 指定されたIDのユーザーが存在するか
    if (!array_key_exists($id, $users)) {
        // レスポンスの作成
        $response = [
            'status' => 'error',
            'message' => '指定されたIDのユーザーが見つかりません'
        ];

        // HTTP ステータスコードの設定(404 Not Found)
        http_response_code(404);

        // 配列をJSON文字列に変換して出力
        echo json_encode($response, JSON_UNESCAPED_UNICODE);
        return;
    }

    // レスポンスの作成
    $response = [
        'status' => 'success',
        'message' => 'ユーザーデータを取得しました',
        'data' => $users[$id]
    ];

    // HTTP ステータスコードの設定(200 OK)
    http_response_code(200);

    // 配列をJSON文字列に変換して出力
    echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}

これにより、/api.php/2 のように、IDを指定して、
単一のデータが取得できるようになりました。

POST メソッド処理の改良

api.php
function handlePost(): void
{
    // JSON で送られてきているか
    $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
    if (!str_starts_with($contentType, 'application/json')) {
        // レスポンスの作成
        $response = [
            'status' => 'error',
            'message' => 'メディアタイプが不正です'
        ];

        // HTTP ステータスコードの設定(415 Unsupported Media Type)
        http_response_code(415);

        // 配列をJSON文字列に変換して出力
        echo json_encode($response, JSON_UNESCAPED_UNICODE);
        return;
    }

    // 送られてきた値を取得
    $rawInput = file_get_contents('php://input');
    $data = json_decode($rawInput, true);

    // バリデーション
    if (!is_array($data) || empty($data['name']) || empty($data['email'])) {

        // レスポンスの作成
        $response = [
            'status' => 'error',
            'message' => 'name と email が必須です'
        ];

        // HTTP ステータスコードの設定(400 Bad Request)
        http_response_code(400);

        // 配列をJSON文字列に変換して出力
        echo json_encode($response, JSON_UNESCAPED_UNICODE);
        return;
    }

    // ID の生成(今回はランダム)
    $newId = random_int(1,100);

    // レスポンスの作成
    $response = [
        'status' => 'success',
        'message' => 'ユーザーデータを登録しました',
        'data' => ['id' => $newId] + $data
    ];

    // HTTP ステータスコードの設定(201 Created)
    http_response_code(201);

    // 配列をJSON文字列に変換して出力
    echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}

補足:よりモダンな JSON デコード(PHP 7.3〜)

今回は解説をシンプルにするため !is_array($data) でチェックを行っていますが、
PHP 7.3 以降では JSON_THROW_ON_ERROR オプションを使う方法も一般的です。

try {
    // 不正な JSON の場合に JsonException 例外を投げる
    $data = json_decode($rawInput, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    http_response_code(400);
    echo json_encode([
        'status' => 'error',
        'message' => 'Invalid JSON format'
    ], JSON_UNESCAPED_UNICODE);
    exit;
}

不正な JSON が送信された際に try-catch で明確に捕捉できるため、
実務やフレームワーク内部ではこの書き方がよく使われます。

補足:メールアドレスのバリデーション

今回はメールアドレスのパターンはチェックしていませんが、
メールアドレス自体のバリデーションチェックを行いたい場合、
filter_var($email, FILTER_VALIDATE_EMAIL) という書き方が使えます。

4. 改良版の動作確認

GET 改良版のテスト

全件取得のテスト

http://localhost:8000/api.phpGET メソッドのリクエストを送ってください。

以下のように表示されれば成功です。

{
    "status": "success",
    "message": "ユーザーデータを取得しました",
    "data": [
        { "id": 1, "name": "田中太郎", "email": "taro@example.com" },
        { "id": 2, "name": "鈴木花子", "email": "hanako@example.com" }
    ]
}

1件取得のテスト

http://localhost:8000/api.php/2GET メソッドのリクエストを送ってください。

以下のように表示されれば成功です。

{
    "status": "success",
    "message": "ユーザーデータを取得しました",
    "data": {
        "id": 2,
        "name": "鈴木花子",
        "email": "hanako@example.com"
    }
}

POST 改良版のテスト

http://localhost:8000/api.php に、POST メソッドで、以下のボディーを送ってください。
ヘッダーは Content-Type: application/json です。

{
    "name": "高橋次郎",
    "email": "jiro@example.com"
}

以下のように表示されれば成功です。(ID はランダムです。)

{
    "status": "success",
    "message": "ユーザーデータを登録しました",
    "data": {
        "id": 87,
        "name": "高橋次郎",
        "email": "jiro@example.com"
    }
}

なぜフレームワークを使うのか?

実際に API を作る際には、Laravel などの フレームワーク がよく使われ、
今回のように素の PHP を使うことはとても少ないです。
この理由について、少しだけ解説します。

実際の Web 開発で、規模が大きくなってくると、次のような負担が出てきます。

  • URL(ルーティング)の管理
    • /users/1/products/10/reviews など、
      URLが増えるたびにファイルを作ったり条件分岐を書いたりする必要が出てくる。
  • データベース操作とセキュリティー
    • SQLの実行やエスケープ処理を毎回安全に書かなければいけない。
  • 共通処理
    • 認証(ログイン状態の確認)やエラーハンドリングを全ファイルに書く必要が出てくる。

フレームワークを使うと、これらの負担が軽減され、より安全・快適に扱えるようになるのです。
そのぶん、開発者が本質的なビジネスロジック(処理)に集中できるようになるわけですね。

API の仕組みを理解していれば、フレームワークを使っても
「この裏で何が起こっているか」を考えながら実装できると思います。
ぜひ、次はフレームワークを使ってみてください。

まとめ

  • レスポンス設定
    • header('Content-Type: application/json') をセットする
  • GETデータ取得(パスパラメータ)
    • $_SERVER['PATH_INFO'] から URL のパスを取得・解析する
      (環境によっては REQUEST_URI を使う方法もある)
  • POSTデータ取得
    • $_POST ではなく file_get_contents('php://input') から生のJSONを取得し、
      json_decode する
  • 日本語対応
    • json_encode() 実行時に JSON_UNESCAPED_UNICODE を指定する
  • ステータスコード
    • 処理成功時は 200201、エラー時は 400404415 など、
      適切な HTTP ステータスコードを http_response_code() で明示する

おわりに

この記事が皆さんの役に立つと嬉しいです!
もし「参考になった」と思ったら、ストック・いいねしていただけますと幸いです。

※この記事は、一部 Google Gemini を利用して作成しました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?