mcpField
S
spring-boot-rest-api-standards

spring-boot-rest-api-standards

github.com/giuseppe-trisciuoglio開発3654.2k 回インストール7 週間前に更新
リポジトリ

spring-boot-rest-api-standards について

Spring Boot 開発者向けの REST API 設計スキルです。エンドポイント、DTO、入力検証、エラー処理、ページネーション、安全ヘッダーを扱い、実装や設計レビューに使えます。

主な機能

  1. 01リソース URL と HTTP メソッドの規約
  2. 02ステータスコードとエラーレスポンス設計
  3. 03DTO と Jakarta 検証
  4. 04ページネーション、フィルタリング、安全ヘッダー

活用シーン

  1. 01REST エンドポイントと API ルートの作成
  2. 02DTO と API 契約の設計
  3. 03グローバルなエラー処理と入力検証の実装
  4. 04REST API アーキテクチャのレビュー

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill spring-boot-rest-api-standards

インストール後、Claude Code で/spring-boot-rest-api-standardsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
architecture-patterns.md
examples.md
http-reference.md
pagination-and-filtering.md
references.md
security-headers.md
spring-web-annotations.md

詳細

Spring Boot REST API 標準

概要

Spring Boot を使用した REST API の設計標準です。URL 設計、HTTP メソッド、ステータスコード、DTO、バリデーション、エラー処理、ページネーション、セキュリティヘッダーを扱います。

使用する場面

  • REST エンドポイントと API ルートを作成するとき
  • DTO と API コントラクトを設計するとき
  • エラー処理とバリデーションを実装するとき
  • ページネーションとフィルタリングを設定するとき
  • セキュリティヘッダーと CORS を設定するとき
  • REST API アーキテクチャをレビューするとき

手順

RESTful API エンドポイントを構築するには

適切に設計された REST API エンドポイントを作成するには、次の手順に従います。

  1. リソースベースの URL を設計する

    • リソース名には複数形の名詞を使用します
    • REST の規約に従います:GET /users、POST /users、PUT /users/{id}
    • /getUserList のようなアクションベースの URL は避けます
  2. 適切な HTTP メソッドを実装する

    • GET:リソースを取得します(安全で、べき等です)
    • POST:リソースを作成します(べき等ではありません)
    • PUT:リソース全体を置き換えます(べき等です)
    • PATCH:リソースを部分的に更新します(べき等ではありません)
    • DELETE:リソースを削除します(べき等です)
  3. 適切なステータスコードを使用する

    • 200 OK:GET/PUT/PATCH の成功
    • 201 Created:Location ヘッダーを含む POST の成功
    • 204 No Content:DELETE の成功
    • 400 Bad Request:リクエストデータが無効です
    • 404 Not Found:リソースが存在しません
    • 409 Conflict:リソースが重複しています
    • 500 Internal Server Error:予期しないエラーです
  4. リクエスト/レスポンス DTO を作成する

    • API コントラクトとドメインエンティティを分離します
    • Java のレコードまたは Lombok @Data/@Value を使用します
    • Jakarta のバリデーションアノテーションを適用します
    • 可能な場合は DTO を不変にします
  5. バリデーションを実装する

    • @RequestBody パラメーターに @Valid アノテーションを使用します
    • バリデーション制約(@NotBlank、@Email、@Size など)を適用します
    • MethodArgumentNotValidException でバリデーションエラーを処理します
  6. エラー処理を設定する

    • グローバルな例外処理に @RestControllerAdvice を使用します
    • ステータス、エラー、メッセージ、タイムスタンプを含む標準化されたエラーレスポンスを返します
    • 特定の HTTP ステータスコードには ResponseStatusException を使用します
  7. ページネーションを設定する

    • 大規模なデータセットには Pageable を使用します
    • page、size、sort の各パラメーターを含めます
    • 総要素数や総ページ数などのメタデータを返します
  8. セキュリティヘッダーを追加する

    • CORS ポリシーを設定します
    • コンテンツセキュリティポリシーを設定します
    • X-Frame-Options、X-Content-Type-Options を含めます

検証チェックポイント:

  • 手順 1~2 の後:URL 構造が REST の規約に従っていることを確認します(/getUsers ではなく /users)
  • 手順 3 の後:各エンドポイントが正しいステータスコードを返すことをテストします
  • 手順 4~5 の後:次に進む前に、curl または HTTPie で DTO を検証します
  • 手順 6 の後:エラーレスポンスが標準化された形式に一致することを確認します

例

基本的な CRUD コントローラー
@RestController
@RequestMapping("/v1/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {
    private final UserService userService;

    @GetMapping
    public ResponseEntity<Page<UserResponse>> getAllUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int pageSize) {
        log.debug("Fetching users page {} size {}", page, pageSize);
        Page<UserResponse> users = userService.getAll(page, pageSize);
        return ResponseEntity.ok(users);
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getById(id));
    }

    @PostMapping
    public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
        UserResponse created = userService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserResponse> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}
リクエスト/レスポンス DTO
// Request DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {
    @NotBlank(message = "User name cannot be blank")
    private String name;

    @Email(message = "Valid email required")
    private String email;
}

// Response DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {
    private Long id;
    private String name;
    private String email;
    private LocalDateTime createdAt;
}
グローバル例外ハンドラー
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(
            MethodArgumentNotValidException ex, WebRequest request) {
        String errors = ex.getBindingResult().getFieldErrors().stream()
                .map(f -> f.getField() + ": " + f.getDefaultMessage())
                .collect(Collectors.joining(", "));

        ErrorResponse errorResponse = new ErrorResponse(
                HttpStatus.BAD_REQUEST.value(),
                "Validation Error",
                "Validation failed: " + errors,
                request.getDescription(false).replaceFirst("uri=", "")
        );
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity<ErrorResponse> handleResponseStatusException(
            ResponseStatusException ex, WebRequest request) {
        ErrorResponse error = new ErrorResponse(
            ex.getStatusCode().value(),
            ex.getStatusCode().toString(),
            ex.getReason(),
            request.getDescription(false).replaceFirst("uri=", "")
        );
        return new ResponseEntity<>(error, ex.getStatusCode());
    }
}

ベストプラクティス

1. コンストラクターインジェクションを使用する
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
}
2. 不変 DTO(Java のレコードまたは @Value)を優先する
public record UserResponse(Long id, String name, String email) {}
3. 適切なトランザクション管理を実装する
@Service
@Transactional
public class UserService {
    @Transactional(readOnly = true)
    public Optional<User> findById(Long id) { return userRepository.findById(id); }

    @Transactional
    public User create(User user) { return userRepository.save(user); }
}

制約と警告

  1. エンティティを直接公開しないでください - DTO を使用して API コントラクトとドメインモデルを分離します
  2. REST の規約に従ってください - リソースには名詞(/users)を使用し、正しい HTTP メソッド、複数形の名前、適切なステータスコードを用います
  3. すべての例外をグローバルに処理してください - @RestControllerAdvice を使用し、生の例外をそのまま伝播させないでください
  4. 大規模な結果セットは必ずページネーションしてください - パフォーマンスの問題や DDoS の脆弱性を防ぎます
  5. すべての入力データを検証してください - リクエスト DTO に Jakarta のバリデーションアノテーションを使用します
  6. 機密データを公開しないでください - パスワード、トークン、PII をログに記録したり、公開したりしないでください

参考資料

  • HTTP ステータスコード、Spring アノテーション、詳細な例など、包括的な参考資料については、references/ ディレクトリを参照してください
  • コードレビューのガイドラインについては、developer-kit-java:spring-boot-code-review-expert エージェントを参照してください
  • 依存性注入のパターンについては、spring-boot-dependency-injection/SKILL.md を確認してください
  • REST API のテストについては、../spring-boot-test-patterns/SKILL.md を確認してください

コメント