본문으로 건너뛰기
cccskills
무료GitHub에 공개

rest-api-conventions

Use when generating REST controllers, DTOs, success response contracts, pagination, HTTP status mapping, or API versioning. For RFC 9457 exception and error response formatting, use problem-details-rfc9457 unless the project explicitly requires a legacy error envelope.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(7)

  • SKILL.md7.0 KB
  • agents/openai.yaml202 B
  • examples/bad-controller.java1.3 KB
  • examples/good-controller.java1.1 KB
  • templates/ApiResponse.java925 B
  • templates/GlobalExceptionHandler.java3.3 KB
  • templates/PageResponse.java546 B

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

REST API Conventions

Project Contract

Inspect existing controllers, tests and OpenAPI before choosing a response contract. Preserve the project's IDs, response shape and versioning strategy. Do not migrate unrelated endpoints while adding one route. The envelope below is an optional convention for a project that uses it; plain success DTOs are equally valid. A 204 response has no body.

Success and error formats are independent: an existing success envelope can coexist with RFC 9457 errors. Use one consistent error policy; choose the legacy error examples below only when the project already requires that format.

Example success envelope:

{
  "success": true,
  "data": { },
  "error": null,
  "timestamp": "2026-04-13T10:00:00Z"
}

Error response:

{
  "success": false,
  "data": null,
  "error": {
    "code": "ORDER_NOT_FOUND",
    "message": "Order with id 123 not found",
    "details": []
  },
  "timestamp": "2026-04-13T10:00:00Z"
}

ApiResponse Wrapper

@JsonInclude(JsonInclude.Include.NON_NULL)
public record ApiResponse<T>(
    boolean success,
    T data,
    ApiError error,
    Instant timestamp
) {
    public static <T> ApiResponse<T> ok(T data) {
        return new ApiResponse<>(true, data, null, Instant.now());
    }

    public static <T> ApiResponse<T> error(String code, String message) {
        return new ApiResponse<>(false, null, new ApiError(code, message, List.of()), Instant.now());
    }
}

public record ApiError(String code, String message, List<String> details) {}

HTTP Status Mapping

ScenarioStatus
GET — found200
POST — created resource201
PUT/PATCH — updated200
DELETE — deleted204 (no body)
Validation failure400
Unauthenticated401
Forbidden403
Not found404
Conflict (duplicate)409
Unhandled server error500

URL Conventions

  • Plural nouns for resources: /orders, /users, /products
  • Kebab-case for multi-word: /order-items, not /orderItems
  • Versioning in path: /api/v1/orders
  • Nested resources max 2 levels: /orders/{id}/items ✅, /orders/{id}/items/{itemId}/notes ❌ — flatten to /order-item-notes/{id}
  • IDs: preserve the existing ID type. UUIDs are an option, not an authorization mechanism.
GET    /api/v1/orders              → list (paginated)
POST   /api/v1/orders              → create
GET    /api/v1/orders/{id}         → get one
PUT    /api/v1/orders/{id}         → full update
PATCH  /api/v1/orders/{id}         → partial update
DELETE /api/v1/orders/{id}         → delete
GET    /api/v1/orders/{id}/items   → nested resource

Pagination

{
  "success": true,
  "data": {
    "content": [...],
    "page": 0,
    "size": 20,
    "totalElements": 150,
    "totalPages": 8,
    "last": false
  }
}

Query params: ?page=0&size=20&sort=createdAt,desc

Use Spring Data Pageable in controllers:

@GetMapping
public ApiResponse<PageResponse<OrderResponse>> list(Pageable pageable) {
    return ApiResponse.ok(PageResponse.from(orderService.findAll(pageable).map(OrderResponse::from)));
}

Cap the page size. A bare Pageable accepts ?size=100000 from any client — one request can drag your whole table into memory. Spring's default cap is 2000, still too high for most APIs:

spring:
  data:
    web:
      pageable:
        default-page-size: 20
        max-page-size: 100   # requests above this are silently clamped

The compiled PageResponse fixes the JSON pagination contract. Mapping entities to DTOs alone does not stabilize Spring Data PageImpl serialization. Spring Data's org.springframework.data.web.PagedModel is another option when its shape fits the API. Validate sort fields against an allowlist and add an ID tie-breaker to non-unique sorts.

Legacy Envelope Exception Handler

// Extends ResponseEntityExceptionHandler: without it the Exception.class catch-all turns
// malformed JSON, 405, bad path variables and unknown URLs into 500. Full version, verified
// against Boot 3 and 4: templates/GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<ApiResponse<Void>> handleNotFound(EntityNotFoundException ex) {
        return ResponseEntity.status(404).body(ApiResponse.error("NOT_FOUND", ex.getMessage()));
    }

    @ExceptionHandler(AccessDeniedException.class) // @PreAuthorize denials surface here, not in the filter
    public ResponseEntity<ApiResponse<Void>> handleAccessDenied(AccessDeniedException ex) {
        return ResponseEntity.status(403).body(ApiResponse.error("FORBIDDEN", "Access denied"));
    }

    @Override // an @ExceptionHandler for this type would clash with the superclass at startup
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        List<String> details = ex.getBindingResult().getFieldErrors().stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage()).toList();
        return ResponseEntity.status(400).body(ApiResponse.validationError(details));
    }

    @Override // keep the framework's status + headers, return the envelope
    protected ResponseEntity<Object> handleExceptionInternal(
            Exception ex, Object body, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        return ResponseEntity.status(status).headers(headers)
            .body(ApiResponse.error("HTTP_" + status.value(), "Request could not be processed"));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Void>> handleGeneric(Exception ex) {
        return ResponseEntity.status(500).body(ApiResponse.error("INTERNAL_ERROR", "An unexpected error occurred"));
    }
}

Gotchas

  • Agent writes an Exception.class catch-all in an advice that doesn't extend ResponseEntityExceptionHandler — malformed JSON, 405, type-mismatched path variables and unknown URLs all become 500
  • Agent imposes a new envelope - preserve the existing success contract.
  • Agent invents competing error handlers - use the project's established error contract.
  • Agent puts exception handlers in controllers — always use @RestControllerAdvice
  • Agent changes ID types while adding an endpoint - retain the existing ID scheme.
  • Agent accepts unbounded Pageable — set spring.data.web.pageable.max-page-size or one request can pull the whole table
  • Agent serializes PageImpl directly - map entities to DTOs, then wrap in PageResponse.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when adding Spring AI-specific model observations, token usage, latency, externally configured cost attribution, advisor telemetry, or protected prompt and completion logging. Use production-observability for general service metrics, health, logs, and OTLP setup.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rrezartprebreza/spring-boot-skills3132026년 10월 17일 업데이트

Use when versioning Spring MVC or WebFlux APIs in Spring Boot 4 / Spring Framework 7. Covers built-in mapping versions, request version resolution, defaults, supported versions, and deprecation headers.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rrezartprebreza/spring-boot-skills3132026년 10월 17일 업데이트

Use when versioning Spring MVC or WebFlux APIs in Spring Boot 3 / Spring Framework 6. Covers explicit URL, header, and media-type strategies, compatibility rules, and deprecation handling.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rrezartprebreza/spring-boot-skills3132026년 10월 17일 업데이트

Use when introducing or correcting grouped Spring Boot configuration, typed property binding, validation, profiles or secret injection. Do not rewrite unrelated single-value configuration.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rrezartprebreza/spring-boot-skills3132026년 10월 17일 업데이트

Use when packaging a Spring Boot 3 application as an OCI image or GraalVM native executable. Covers buildpacks, layered images, JVM containers, AOT hints, probes, security, and verification.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rrezartprebreza/spring-boot-skills3132026년 10월 17일 업데이트

Use when packaging Spring Boot 4 as an OCI image, JVM container, AOT application, or GraalVM native executable. Covers buildpacks, layers, runtime hints, probes, security, and verification.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

rrezartprebreza/spring-boot-skills3132026년 10월 17일 업데이트

rrezartprebreza의 스킬 모두 보기

이 스킬의 문제 신고