api-endpoint
Creates REST API endpoints following project conventions with proper HTTP methods, validation, error handling, Swagger documentation, and security
安装与使用
请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。
查看来源 ↗使用指南技能说明
API Endpoint Creation Skill
This skill creates REST API endpoints following ActivityTracking conventions.
When to Use
- Creating new REST API endpoints
- Need proper HTTP semantics and validation
- Adding API documentation
- Implementing CRUD operations
API Design Principles
RESTful Conventions
- GET - Retrieve resources (idempotent, safe)
- POST - Create resources (non-idempotent)
- PUT - Update entire resource (idempotent)
- PATCH - Partial update (non-idempotent)
- DELETE - Remove resource (idempotent)
HTTP Status Codes
- 200 OK - Successful GET, PUT, PATCH
- 201 Created - Successful POST
- 204 No Content - Successful DELETE
- 400 Bad Request - Validation error
- 401 Unauthorized - Missing/invalid token
- 403 Forbidden - Insufficient permissions
- 404 Not Found - Resource not found
- 409 Conflict - Duplicate resource
- 500 Internal Server Error - Server error
Endpoint Creation Process
Step 1: Define Resource Model
/**
* DTO for MyResource API requests/responses.
*
* @author Dean Ammons
* @version 1.0
* @since January 2026
*/
public class MyResourceDTO {
@NotNull(message = "ID is required")
private Long id;
@NotBlank(message = "Name is required")
@Size(min = 3, max = 100, message = "Name must be 3-100 characters")
private String name;
@Email(message = "Invalid email format")
private String email;
@Min(value = 0, message = "Value must be positive")
private BigDecimal value;
// Getters and setters with explicit access modifiers
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
// ... rest of getters/setters
}
Step 2: Create REST Controller
package com.ammons.taskactivity.controller;
/**
* REST API controller for MyResource operations.
* Provides CRUD endpoints with proper HTTP semantics, validation, and security.
*
* @author Dean Ammons
* @version 1.0
* @since January 2026
*/
@RestController
@RequestMapping("/api/myresources")
@CrossOrigin(origins = "*")
@Tag(name = "MyResource", description = "MyResource management APIs")
public class MyResourceController {
private final MyResourceService service;
public MyResourceController(MyResourceService service) {
this.service = service;
}
/**
* Retrieves all resources for the authenticated user.
*
* @param principal Authenticated user principal
* @return List of user's resources
*/
@GetMapping
@Operation(summary = "Get all resources", description = "Retrieves all resources for authenticated user")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Successfully retrieved resources"),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
public ResponseEntity<List<MyResourceDTO>> getAll(Principal principal) {
Long userId = extractUserId(principal);
List<MyResourceDTO> resources = service.findByUserId(userId);
return ResponseEntity.ok(resources);
}
/**
* Retrieves a specific resource by ID.
*
* @param id Resource ID
* @param principal Authenticated user principal
* @return The requested resource
*/
@GetMapping("/{id}")
@Operation(summary = "Get resource by ID")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Successfully retrieved resource"),
@ApiResponse(responseCode = "404", description = "Resource not found"),
@ApiResponse(responseCode = "403", description = "Access denied")
})
public ResponseEntity<MyResourceDTO> getById(
@PathVariable @Positive Long id,
Principal principal) {
Long userId = extractUserId(principal);
return service.findByIdAndUserId(id, userId)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
/**
* Creates a new resource.
*
* @param dto Resource data
* @param principal Authenticated user principal
* @return The created resource with generated ID
*/
@PostMapping
@Operation(summary = "Create new resource")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "Successfully created resource"),
@ApiResponse(responseCode = "400", description = "Validation error"),
@ApiResponse(responseCode = "409", description = "Resource already exists")
})
public ResponseEntity<MyResourceDTO> create(
@Valid @RequestBody MyResourceDTO dto,
Principal principal) {
Long userId = extractUserId(principal);
MyResourceDTO created = service.create(dto, userId);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
return ResponseEntity.created(location).body(created);
}
/**
* Updates an existing resource.
*
* @param id Resource ID
* @param dto Updated resource data
* @param principal Authenticated user principal
* @return The updated resource
*/
@PutMapping("/{id}")
@Operation(summary = "Update existing resource")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Successfully updated resource"),
@ApiResponse(responseCode = "404", description = "Resource not found"),
@ApiResponse(responseCode = "400", description = "Validation error")
})
public ResponseEntity<MyResourceDTO> update(
@PathVariable @Positive Long id,
@Valid @RequestBody MyResourceDTO dto,
Principal principal) {
Long userId = extractUserId(principal);
return service.update(id, dto, userId)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
/**
* Deletes a resource.
*
* @param id Resource ID
* @param principal Authenticated user principal
* @return No content on success
*/
@DeleteMapping("/{id}")
@Operation(summary = "Delete resource")
@ApiResponses({
@ApiResponse(responseCode = "204", description = "Successfully deleted resource"),
@ApiResponse(responseCode = "404", description = "Resource not found")
})
public ResponseEntity<Void> delete(
@PathVariable @Positive Long id,
Principal principal) {
Long userId = extractUserId(principal);
boolean deleted = service.delete(id, userId);
return deleted
? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
}
/**
* Extracts user ID from authenticated principal.
*
* @param principal Authenticated user principal
* @return User ID
*/
private Long extractUserId(Principal principal) {
if (principal == null) {
throw new UnauthorizedException("User not authenticated");
}
// Extract from JWT claims
return JwtUtils.getUserIdFromPrincipal(principal);
}
}
Step 3: Add Validation
/**
* Custom validator for MyResource business rules.
*
* @author Dean Ammons
* @version 1.0
* @since January 2026
*/
@Component
public class MyResourceValidator {
public void validateCreate(MyResourceDTO dto) {
List<String> errors = new ArrayList<>();
if (dto.getName() == null || dto.getName().trim().isEmpty()) {
errors.add("Name is required");
}
if (dto.getValue() != null && dto.getValue().compareTo(BigDecimal.ZERO) < 0) {
errors.add("Value must be positive");
}
if (!errors.isEmpty()) {
throw new ValidationException("Validation failed: " + String.join(", ", errors));
}
}
}
Step 4: Add Error Handling
/**
* Global exception handler for API errors.
*
* @author Dean Ammons
* @version 1.0
* @since January 2026
*/
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidationException(ValidationException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Validation Error",
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
"Resource Not Found",
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDenied(AccessDeniedException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.FORBIDDEN.value(),
"Access Denied",
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex) {
Map<String, String> fieldErrors = ex.getBindingResult()
.getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
error -> error.getDefaultMessage() != null ? error.getDefaultMessage() : "Invalid"
));
ErrorResponse error = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"Validation Error",
"Invalid request parameters",
LocalDateTime.now(),
fieldErrors
);
return ResponseEntity.badRequest().body(error);
}
}
Step 5: Add Swagger Documentation
/**
* OpenAPI configuration for Swagger documentation.
*
* @author Dean Ammons
* @version 1.0
* @since January 2026
*/
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("TaskActivity API")
.version("1.0.0")
.description("REST API for Task and Activity tracking")
.contact(new Contact()
.name("Dean Ammons")
.email("dean@example.com")))
.addSecurityItem(new SecurityRequirement().addList("JWT"))
.components(new Components()
.addSecuritySchemes("JWT", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")));
}
}
API Testing
Integration Test
/**
* Integration tests for MyResource API endpoints.
*
* @author Dean Ammons
* @version 1.0
* @since January 2026
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class MyResourceControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
private String jwtToken;
@BeforeEach
void setUp() {
jwtToken = generateTestJwtToken();
}
@Test
@DisplayName("Should create resource successfully")
void shouldCreateResource() throws Exception {
MyResourceDTO dto = new MyResourceDTO();
dto.setName("Test Resource");
dto.setValue(BigDecimal.valueOf(100.00));
mockMvc.perform(post("/api/myresources")
.header("Authorization", "Bearer " + jwtToken)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.name").value("Test Resource"));
}
@Test
@DisplayName("Should return 400 for invalid request")
void shouldReturn400ForInvalidRequest() throws Exception {
MyResourceDTO dto = new MyResourceDTO();
// Missing required name field
mockMvc.perform(post("/api/myresources")
.header("Authorization", "Bearer " + jwtToken)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("Validation Error"));
}
}
Security Configuration
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/api/**").authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
API Conventions Checklist
- Controller uses @RestController
- Endpoints follow REST conventions (/api/resources)
- HTTP methods used correctly (GET, POST, PUT, DELETE)
- Proper status codes returned
- Request validation with @Valid
- Error handling with @RestControllerAdvice
- Authentication required (JWT)
- Authorization checks (user can only access their data)
- Swagger documentation with @Operation
- Integration tests for all endpoints
- CORS configured properly
- Rate limiting on sensitive endpoints
Common Patterns
Pagination
@GetMapping
public ResponseEntity<Page<MyResourceDTO>> getAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
Principal principal) {
Long userId = extractUserId(principal);
Pageable pageable = PageRequest.of(page, size);
Page<MyResourceDTO> resources = service.findByUserId(userId, pageable);
return ResponseEntity.ok(resources);
}
Filtering
@GetMapping("/search")
public ResponseEntity<List<MyResourceDTO>> search(
@RequestParam String keyword,
@RequestParam(required = false) LocalDate startDate,
@RequestParam(required = false) LocalDate endDate,
Principal principal) {
Long userId = extractUserId(principal);
List<MyResourceDTO> resources = service.search(userId, keyword, startDate, endDate);
return ResponseEntity.ok(resources);
}
Bulk Operations
@PostMapping("/bulk")
public ResponseEntity<BulkOperationResult> bulkCreate(
@Valid @RequestBody List<MyResourceDTO> dtos,
Principal principal) {
Long userId = extractUserId(principal);
BulkOperationResult result = service.bulkCreate(dtos, userId);
return ResponseEntity.ok(result);
}
Memory Bank References
- Check
ai/architecture-patterns.mdfor API design patterns - Check
ai/common-patterns.mdfor controller templates - Check
docs/Swagger_API_Guide.mdfor API documentation standards
使用示例
查看来源仓库中的使用示例与配置说明。
查看来源 ↗来源历史
- 收录时间
- 2026-01-20
- 索引更新时间
- 2026-01-26
- Branch
- main
收录与同步日期属于本站记录。源码修改历史请查看来源仓库。
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!