404 Not Found

404 Not Found


nginx

REST APIクイックスタートガイド

REST APIはマイクロサービスの共通言語です。HTTP動詞でリソースを操作し, JSONでデータを転送することで, シンプル, 標準的, かつ効率的に通信できます。

1. 学ぶ内容


2. API開発者の実話

(1) ペインポイント:API仕様の不統一

Aliceは既存のプロジェクトチームに参加し, REST APIの設計が混乱していることを発見しました。一部のメソッドはGETでデータを変更し, URLの命名が不統一 (/getOrder, /order/list, /deleteOrderById)で, レスポンス形式も標準化されていませんでした。JSONを返すものもあればXMLを返すものもあり, エラーレスポンスはプレーンテキストでした。フロントエンド開発者のBobは, 新しいAPIを統合するたびにバックエンドチームに確認する必要があり, コミュニケーションに多大な時間を浪費していました。

(2) RESTfulアプローチ

RESTは統一された規約のセットでAPI設計を定義します。

JAVA
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    @GetMapping("/{id}")      // GET    /api/orders/123
    @PostMapping              // POST   /api/orders
    @PutMapping("/{id}")      // PUT    /api/orders/123
    @DeleteMapping("/{id}")   // DELETE /api/orders/123
}

(3) 成果

AliceがOrderFlowのAPIをRESTfulスタイルにリファクタリングした後, URLの命名規則が統一され, HTTP動詞のセマンティクスが明確になりました。フロントエンド開発者のBobは「URLを見るだけで使い方がわかる」と言い, API統合に要する時間は平均2時間から15分に短縮されました。


3. RESTの核心概念

(1) RESTの6つの原則

REST (Representational State Transfer)はこのアーキテクチャスタイルの核心的な制約を定義します。

100%
graph TD
    A[RESTの制約] --> B[クライアント・サーバー<br/>関心の分離]
    A --> C[ステートレス<br/>各リクエストに<br/>必要な情報をすべて含む]
    A --> D[キャッシュ可能<br/>レスポンスが<br/>キャッシュ性を定義]
    A --> E[統一インターフェース<br/>一貫したAPI設計]
    A --> F[階層化システム<br/>クライアントは直接接続か<br/>どうかを区別できない]
    A --> G[オンデマンドコード<br/>オプション:サーバーが<br/>実行可能コードを送信可能]

(2) HTTP動詞とCRUD操作のマッピング

HTTP動詞 操作 べき等性 安全性 代表的なURL
GET 照会 あり あり /api/orders
POST 作成 なし なし /api/orders
PUT 全体更新 あり なし /api/orders/123
PATCH 部分更新 なし なし /api/orders/123
DELETE 削除 あり なし /api/orders/123
📌 重要ポイント: べき等性とは, 操作を複数回実行しても同じ結果が得られることを意味します。PUTはべき等 (リソース全体を置換)ですが, POSTはべき等ではありません (毎回新しいリソースが作成される可能性があります)。


4. @RestControllerとリクエストマッピング

(1) @RestControllerの仕組み

@RestController = @Controller + @ResponseBody。このクラスのすべてのメソッドの戻り値がHTTPレスポンスボディに直接書き込まれる(デフォルトでJSON)ことを示します。

(1) ▶ サンプル:基本的なOrderController

JAVA
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final List<Map<String, Object>> orders = new ArrayList<>();

    @GetMapping
    public List<Map<String, Object>> listOrders() {
        return orders;
    }

    @PostMapping
    public Map<String, Object> createOrder(
            @RequestBody Map<String, Object> order) {
        order.put("id", (long) (orders.size() + 1));
        order.put("status", "PENDING");
        orders.add(order);
        return order;
    }
}

出力:

TEXT
// 実行成功

(2) リクエストパラメータのバインディング

(2) ▶ サンプル:@PathVariableと@RequestParam

JAVA
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @GetMapping("/{id}")
    public Map<String, Object> getOrder(@PathVariable Long id) {
        return Map.of("id", id, "status", "PENDING");
    }

    @GetMapping
    public List<Map<String, Object>> searchOrders(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size,
            @RequestParam(required = false) String status) {
        return List.of(Map.of(
            "page", page, "size", size, "status", status
        ));
    }
}

出力:

TEXT
// 実行成功
アノテーション 用途 URL
@PathVariable パスから変数を抽出 @PathVariable Long id /api/orders/123
@RequestParam クエリパラメータを抽出 @RequestParam String status /api/orders?status=PENDING
@RequestBody リクエストボディからJSONを抽出 @RequestBody OrderRequest req POSTボディ
@RequestHeader リクエストヘッダーを抽出 @RequestHeader String auth Header: Authorization

(3) ▶ サンプル:DTOを使ったリクエストボディの受信

JAVA
public record CreateOrderRequest(
    Long productId,
    Integer quantity,
    String shippingAddress
) {}

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @PostMapping
    public ResponseEntity<Map<String, Object>> createOrder(
            @RequestBody CreateOrderRequest request) {
        Map<String, Object> order = Map.of(
            "productId", request.productId(),
            "quantity", request.quantity(),
            "shippingAddress", request.shippingAddress(),
            "status", "PENDING"
        );
        return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(order);
    }
}

出力:

TEXT
// 実行成功

5. ResponseEntityとHTTPステータスコード

(1) ResponseEntityの使用

ResponseEntityによりHTTPレスポンスを完全に制御できます。ステータスコード、レスポンスヘッダー、レスポンスボディを設定できます。

メソッド 適用シナリオ 柔軟性
オブジェクトを直接返す シンプルな成功レスポンス 低 (200で固定)
ResponseEntity.ok(body) 200を設定する場合
ResponseEntity.status(CREATED).body(body) リソース作成で201を返す場合
ResponseEntity.notFound().build() リソース不明で404を返す場合

(1) ▶ サンプル:完全なCRUD操作

JAVA
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final Map<Long, Map<String, Object>> orderStore = new ConcurrentHashMap<>();
    private final AtomicLong idGenerator = new AtomicLong(1);

    @PostMapping
    public ResponseEntity<Map<String, Object>> create(
            @RequestBody CreateOrderRequest request) {
        Long id = idGenerator.getAndIncrement();
        Map<String, Object> order = new HashMap<>();
        order.put("id", id);
        order.put("productId", request.productId());
        order.put("quantity", request.quantity());
        order.put("status", "PENDING");
        orderStore.put(id, order);
        return ResponseEntity.status(HttpStatus.CREATED).body(order);
    }

    @GetMapping("/{id}")
    public ResponseEntity<Map<String, Object>> getOne(@PathVariable Long id) {
        Map<String, Object> order = orderStore.get(id);
        if (order == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(order);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Map<String, Object>> update(
            @PathVariable Long id,
            @RequestBody CreateOrderRequest request) {
        if (!orderStore.containsKey(id)) {
            return ResponseEntity.notFound().build();
        }
        Map<String, Object> order = new HashMap<>();
        order.put("id", id);
        order.put("productId", request.productId());
        order.put("quantity", request.quantity());
        order.put("status", "CONFIRMED");
        orderStore.put(id, order);
        return ResponseEntity.ok(order);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        if (orderStore.remove(id) == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.noContent().build();
    }
}

出力:

TEXT
// 実行成功

6. APIテスト方法

(1) curlテストコマンド

(1) ▶ サンプル:curlでCRUD APIをテスト

BASH
# 注文の作成
curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{"productId":1, "quantity":3, "shippingAddress":"123 Main St"}'

# 注文の取得
curl http://localhost:8080/api/orders/1

# ページネーション付き注文一覧
curl "http://localhost:8080/api/orders?page=0&size=10"

# 注文の更新
curl -X PUT http://localhost:8080/api/orders/1 \
  -H "Content-Type: application/json" \
  -d '{"productId":2, "quantity":5, "shippingAddress":"456 Oak Ave"}'

# 注文の削除
curl -X DELETE http://localhost:8080/api/orders/1

出力:

TEXT
{"status":"ok","data":{}}
HTTPステータスコード 意味 返されるタイミング
200 OK 成功 GET / PUT成功
201 Created 作成 POSTリソース作成成功
204 No Content 内容なし DELETE成功
400 Bad Request リクエストエラー パラメータ検証失敗
404 Not Found 見つからない リソースが存在しない

7. RESTful API設計仕様

(1) URL命名規則

ルール 誤った形式 正しい形式
複数名詞の使用 /getOrder /api/orders
ネストされたリソース関係 /orderItems?orderId=1 /api/orders/1/items
クエリよりパスを使用 /api/orders?id=1 /api/orders/1
バージョニング なし /api/v1/orders
フィルタリングにはクエリパラメータ /api/pendingOrders /api/orders?status=PENDING

8. 総合サンプル:OrderFlow注文CRUD API

JAVA
package com.orderflow.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

public record CreateOrderRequest(
    Long productId, Integer quantity, String shippingAddress
) {}

@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {

    private final Map<Long, Map<String, Object>> store = new ConcurrentHashMap<>();
    private final AtomicLong seq = new AtomicLong(1);

    @PostMapping
    public ResponseEntity<Map<String, Object>> create(
            @RequestBody CreateOrderRequest req) {
        Long id = seq.getAndIncrement();
        Map<String, Object> order = new LinkedHashMap<>();
        order.put("id", id);
        order.put("productId", req.productId());
        order.put("quantity", req.quantity());
        order.put("shippingAddress", req.shippingAddress());
        order.put("status", "PENDING");
        order.put("createdAt", java.time.Instant.now());
        store.put(id, order);
        return ResponseEntity.status(HttpStatus.CREATED).body(order);
    }

    @GetMapping
    public List<Map<String, Object>> list(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        return store.values().stream()
            .skip((long) page * size).limit(size).toList();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Map<String, Object>> get(@PathVariable Long id) {
        Map<String, Object> order = store.get(id);
        return order != null ? ResponseEntity.ok(order)
                             : ResponseEntity.notFound().build();
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        return store.remove(id) != null
            ? ResponseEntity.noContent().build()
            : ResponseEntity.notFound().build();
    }
}

❓ よくある質問

Q @Controllerと@RestControllerの違いは何ですか?
A @Controllerはビュー名を返します (テンプレートエンジンで使用)。@RestController = @Controller + @ResponseBodyで, 戻り値を直接JSONにシリアライズします。REST APIを書く際は常に@RestControllerを使用してください。
Q PUTとPATCHの違いは何ですか?
A PUTは全体置換なので, オブジェクト全体を渡す必要があります。PATCHは部分更新なので, 変更が必要なフィールドだけを渡せばよいです。Spring Bootは両方をサポートしますが, PATCHはカスタムマージロジックが必要です。
Q @PathVariableと@RequestParamはいつ使うべきですか?
A @PathVariableはリソースの一意識別子 (注文IDなど)に使い, @RequestParamはフィルタリングやページネーションパラメータ (ステータスやページなど)に使います。簡単なルール:パスには@PathVariable, クエリパラメータには@RequestParamを使います。
Q ResponseEntityを返すべきか, オブジェクトを直接返すべきですか?
A シンプルな照会ならオブジェクトを直接返すだけで構いません (自動的に200ステータスコードが返されます)。カスタムステータスコード (201や404など)やレスポンスヘッダーを設定する必要がある場合はResponseEntityを使います。
Q 日付・時刻形式はどう扱うべきですか?
A デフォルトではタイムスタンプとしてシリアライズされます。application.ymlで設定するか, フィールドに@JsonFormat(pattern = "yyyy-MM-dd")を使用できます。
Q REST APIにバージョニングは必要ですか?
A URLベースのバージョニング (/api/v1/orders)を推奨します。シンプルで直感的です。ヘッダーベースのバージョニング (Accept: application/vnd.orderflow.v1+json)も可能で, よりRESTfulですが実装が複雑になります。

📖 まとめ


📝 練習問題

  1. 基本問題 (難易度 ⭐):OrderFlowのProductリソースのCRUD APIを実装してください。GET /api/v1/products, POST /api/v1/products, GET /api/v1/products/{id}, DELETE /api/v1/products/{id}を含みます。

  2. 応用問題 (難易度 ⭐⭐):Order APIにPATCH /api/v1/orders/{id}/statusエンドポイントを追加し, 注文ステータスフィールドのみを更新してください。PATCHとPUTの違いについて考察してください。

  3. チャレンジ問題 (難易度 ⭐⭐⭐):GET /api/v1/orders/{id}/itemsネストされたリソースエンドポイントを実装し, 指定した注文のすべての注文項目を返してください。ページネーションパラメータもサポートすること。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%