CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

82

Quality

82%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Validation failed for skills in this tile
One or more skills have errors that need to be fixed before they can move to Implementation and Discovery review.
Overview
Quality
Evals
Security
Files

examples.mdplugins/developer-kit-java/skills/spring-boot-rest-api-standards/references/

Spring Boot REST API Examples

Complete CRUD REST API with Validation

Entity with Validation

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 100, message = "Name must be 2-100 characters")
    private String name;

    @NotBlank(message = "Email is required")
    @Email(message = "Valid email required")
    @Column(unique = true)
    private String email;

    @Min(value = 18, message = "Must be at least 18")
    @Max(value = 120, message = "Invalid age")
    private Integer age;

    @Size(min = 8, max = 100, message = "Password must be 8-100 characters")
    private String password;

    @Column(name = "is_active")
    private Boolean active = true;

    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

Service with Transaction Management

@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class UserService {

    private final UserRepository userRepository;
    private final EmailService emailService;

    @Transactional(readOnly = true)
    public Page<UserResponse> findAll(Pageable pageable) {
        log.debug("Fetching users page {} size {}", pageable.getPageNumber(), pageable.getPageSize());
        return userRepository.findAll(pageable)
                .map(this::toResponse);
    }

    @Transactional(readOnly = true)
    public UserResponse findById(Long id) {
        log.debug("Looking for user with id {}", id);
        return userRepository.findById(id)
                .map(this::toResponse)
                .orElseThrow(() -> new EntityNotFoundException("User not found"));
    }

    @Transactional
    public UserResponse create(CreateUserRequest request) {
        log.info("Creating user with email: {}", request.getEmail());

        if (userRepository.existsByEmail(request.getEmail())) {
            throw new BusinessException("Email already exists");
        }

        User user = new User();
        user.setName(request.getName());
        user.setEmail(request.getEmail());
        user.setAge(request.getAge());
        user.setPassword(passwordEncoder.encode(request.getPassword()));

        User saved = userRepository.save(user);
        emailService.sendWelcomeEmail(saved.getEmail(), saved.getName());

        return toResponse(saved);
    }

    @Transactional
    public UserResponse update(Long id, UpdateUserRequest request) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException("User not found"));

        if (request.getName() != null) {
            user.setName(request.getName());
        }
        if (request.getEmail() != null) {
            if (!user.getEmail().equals(request.getEmail()) &&
                userRepository.existsByEmail(request.getEmail())) {
                throw new BusinessException("Email already exists");
            }
            user.setEmail(request.getEmail());
        }
        if (request.getAge() != null) {
            user.setAge(request.getAge());
        }

        User updated = userRepository.save(user);
        return toResponse(updated);
    }

    @Transactional
    public void delete(Long id) {
        if (!userRepository.existsById(id)) {
            throw new EntityNotFoundException("User not found");
        }

        User user = userRepository.findById(id).orElseThrow();
        emailService.sendDeletionEmail(user.getEmail(), user.getName());
        userRepository.deleteById(id);
    }

    private UserResponse toResponse(User user) {
        return new UserResponse(
            user.getId(),
            user.getName(),
            user.getEmail(),
            user.getAge(),
            user.getActive(),
            user.getCreatedAt(),
            user.getUpdatedAt()
        );
    }
}

Controller with Proper HTTP Methods

@RestController
@RequestMapping("/api/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 size,
            @RequestParam(defaultValue = "createdAt") String sortBy,
            @RequestParam(defaultValue = "DESC") String sortDirection) {

        Sort sort = Sort.by(Sort.Direction.fromString(sortDirection), sortBy);
        Pageable pageable = PageRequest.of(page, size, sort);
        Page<UserResponse> users = userService.findAll(pageable);

        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Total-Count", String.valueOf(users.getTotalElements()));
        headers.add("X-Total-Pages", String.valueOf(users.getTotalPages()));

        return ResponseEntity.ok()
                .headers(headers)
                .body(users);
    }

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

    @PostMapping
    public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
        UserResponse created = userService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED)
                .header("Location", "/api/users/" + created.getId())
                .body(created);
    }

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

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

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

API Versioning Examples

URL Versioning

@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 {
    // Version 1 endpoints
}

@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 {
    // Version 2 endpoints with different response format
}

Header Versioning

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping
    public ResponseEntity<UserResponse> getUsers(
            @RequestHeader(value = "Accept-Version", defaultValue = "1.0") String version) {

        if (version.equals("2.0")) {
            return ResponseEntity.ok(v2UserResponse);
        }
        return ResponseEntity.ok(v1UserResponse);
    }
}

Media Type Versioning

@GetMapping(produces = {
    "application/vnd.company.v1+json",
    "application/vnd.company.v2+json"
})
public ResponseEntity<UserResponse> getUsers(
        @RequestHeader("Accept") String accept) {

    if (accept.contains("v2")) {
        return ResponseEntity.ok(v2UserResponse);
    }
    return ResponseEntity.ok(v1UserResponse);
}

HATEOAS Implementation

Response with Links

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserResponseWithLinks {
    private Long id;
    private String name;
    private String email;
    private Map<String, String> _links;

    // Lombok generates constructors/getters/setters
}

@GetMapping("/{id}")
public ResponseEntity<UserResponseWithLinks> getUserWithLinks(@PathVariable Long id) {
    UserResponse user = userService.findById(id);

    Map<String, String> links = Map.of(
        "self", "/api/users/" + id,
        "all", "/api/users",
        "update", "/api/users/" + id,
        "delete", "/api/users/" + id
    );

    UserResponseWithLinks response = new UserResponseWithLinks(
        user.getId(), user.getName(), user.getEmail(), links);

    return ResponseEntity.ok(response);
}

Advanced HATEOAS with Spring HATEOAS

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;
    private final EntityLinks entityLinks;

    @GetMapping
    public ResponseEntity<CollectionModel<UserResponse>> getAllUsers() {
        List<UserResponse> users = userService.findAll().stream()
                .map(this::toResponse)
                .collect(Collectors.toList());

        CollectionModel<UserResponse> resource = CollectionModel.of(users);
        resource.add(entityLinks.linkToCollectionResource(UserController.class).withSelfRel());
        resource.add(entityLinks.linkToCollectionResource(UserController.class).withRel("users"));

        return ResponseEntity.ok(resource);
    }

    @GetMapping("/{id}")
    public ResponseEntity<EntityModel<UserResponse>> getUserById(@PathVariable Long id) {
        UserResponse user = userService.findById(id);

        EntityModel<UserResponse> resource = EntityModel.of(user);
        resource.add(entityLinks.linkToItemResource(UserController.class, id).withSelfRel());
        resource.add(entityLinks.linkToCollectionResource(UserController.class).withRel("users"));
        resource.add(linkTo(methodOn(UserController.class).getUserOrders(id)).withRel("orders"));

        return ResponseEntity.ok(resource);
    }

    private UserResponse toResponse(User user) {
        return new UserResponse(
            user.getId(),
            user.getName(),
            user.getEmail(),
            user.getActive(),
            user.getCreatedAt()
        );
    }
}

Async Processing

Asynchronous Controller

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class AsyncUserController {

    private final AsyncUserService asyncUserService;

    @GetMapping("/{id}")
    public CompletableFuture<ResponseEntity<UserResponse>> getUserById(@PathVariable Long id) {
        return asyncUserService.getUserById(id)
                .thenApply(ResponseEntity::ok)
                .exceptionally(ex -> ResponseEntity.notFound().build());
    }

    @PostMapping
    public CompletableFuture<ResponseEntity<UserResponse>> createUser(
            @Valid @RequestBody CreateUserRequest request) {
        return asyncUserService.createUser(request)
                .thenApply(created ->
                    ResponseEntity.status(HttpStatus.CREATED)
                        .header("Location", "/api/users/" + created.getId())
                        .body(created))
                .exceptionally(ex -> {
                    if (ex.getCause() instanceof BusinessException) {
                        return ResponseEntity.badRequest().build();
                    }
                    return ResponseEntity.internalServerError().build();
                });
    }
}

Async Service Implementation

@Service
@RequiredArgsConstructor
public class AsyncUserService {

    private final UserService userService;
    private final ExecutorService executor;

    @Async
    public CompletableFuture<UserResponse> getUserById(Long id) {
        return CompletableFuture.supplyAsync(() -> userService.findById(id), executor);
    }

    @Async
    public CompletableFuture<UserResponse> createUser(CreateUserRequest request) {
        return CompletableFuture.supplyAsync(() -> userService.create(request), executor);
    }
}

File Upload and Download

File Upload Controller

@RestController
@RequestMapping("/api/files")
@RequiredArgsConstructor
public class FileController {

    private final FileStorageService fileStorageService;

    @PostMapping("/upload")
    public ResponseEntity<FileUploadResponse> uploadFile(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            throw new BusinessException("File is empty");
        }

        String fileName = fileStorageService.storeFile(file);
        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
                .path("/api/files/download/")
                .path(fileName)
                .toUriString();

        FileUploadResponse response = new FileUploadResponse(
            fileName, fileDownloadUri, file.getContentType(), file.getSize());

        return ResponseEntity.ok(response);
    }

    @GetMapping("/download/{fileName:.+}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
        Resource resource = fileStorageService.loadFileAsResource(fileName);

        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .header(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"" + resource.getFilename() + "\"")
                .body(resource);
    }
}

File Storage Service

@Service
public class FileStorageService {

    private final Path fileStorageLocation;

    @Autowired
    public FileStorageService(FileStorageProperties fileStorageProperties) {
        this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir())
                .toAbsolutePath().normalize();

        try {
            Files.createDirectories(this.fileStorageLocation);
        } catch (Exception ex) {
            throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
        }
    }

    public String storeFile(MultipartFile file) {
        String fileName = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename()));

        try {
            if (fileName.contains("..")) {
                throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
            }

            Path targetLocation = this.fileStorageLocation.resolve(fileName);
            Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);

            return fileName;
        } catch (IOException ex) {
            throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
        }
    }

    public Resource loadFileAsResource(String fileName) {
        try {
            Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
            Resource resource = new UrlResource(filePath);

            if (resource.exists() && resource.isReadable()) {
                return resource;
            } else {
                throw new FileNotFoundException("File not found " + fileName);
            }
        } catch (Exception ex) {
            throw new FileNotFoundException("File not found " + fileName, ex);
        }
    }
}

WebSocket Integration

WebSocket Configuration

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*")
                .withSockJS();
    }
}

WebSocket Controller

@Controller
@RequiredArgsConstructor
public class WebSocketController {

    private final SimpMessagingTemplate messagingTemplate;

    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
        return chatMessage;
    }

    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    public ChatMessage addUser(@Payload ChatMessage chatMessage,
                              SimpMessageHeaderAccessor headerAccessor) {
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        return chatMessage;
    }

    @Scheduled(fixedRate = 5000)
    public void sendPeriodicUpdates() {
        messagingTemplate.convertAndSend("/topic/updates",
            new UpdateMessage("System update", LocalDateTime.now()));
    }
}

Frontend Integration Example

// JavaScript WebSocket client
class WebSocketClient {
    constructor(url) {
        this.url = url;
        this.stompClient = null;
        this.connected = false;
    }

    connect() {
        const socket = new SockJS(this.url);
        this.stompClient = Stomp.over(socket);

        this.stompClient.connect({}, (frame) => {
            this.connected = true;
            console.log('Connected: ' + frame);

            // Subscribe to topics
            this.stompClient.subscribe('/topic/public', (message) => {
                this.onMessage(message);
            });

            this.stompClient.subscribe('/topic/updates', (update) => {
                this.onUpdate(update);
            });
        }, (error) => {
            this.connected = false;
            console.error('Error: ' + error);
        });
    }

    sendMessage(message) {
        if (this.connected) {
            this.stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(message));
        }
    }

    onMessage(message) {
        const chatMessage = JSON.parse(message.body);
        console.log('Received message:', chatMessage);
        // Display message in UI
    }

    onUpdate(update) {
        const updateMessage = JSON.parse(update.body);
        console.log('Received update:', updateMessage);
        // Update UI with system messages
    }
}

plugins

developer-kit-java

skills

README.md

tile.json