CodeGym /Courses /Module 5. Spring /Lecture 155: Setting up controllers and services to handl...

Lecture 155: Setting up controllers and services to handle requests

Module 5. Spring
Level 25 , Lesson 4
Available

Learning Spring MVC is kind of like humanizing a robot: you add ears (controllers to accept requests), a brain (services to handle logic), and hands (repositories to work with the database). Today we'll focus on the ears and the brain. Repositories were already created earlier, so let them deal with direct database interaction.


What are controllers and services in Spring MVC?

Controllers receive incoming requests and decide how to handle them, delegating tasks to services. Services implement business logic and, when needed, talk to repositories (like a pantry with ingredients).

Controller:

  • Responsible for routing requests to the correct handler.
  • Processes incoming data (for example, JSON from an HTTP request).
  • Returns a response to the client.

Service:

  • Performs core business logic.
  • Declares and/or uses repository methods to work with the database.
  • Makes sure functionality is isolated and reusable.

Creating REST controllers: handling incoming HTTP requests

REST controllers are the heart of your web app. They accept HTTP requests (GET, POST, PUT, DELETE) and return responses. You create them with the @RestController annotation, and endpoints are defined using @RequestMapping, @GetMapping, @PostMapping, etc.

Minimal controller example:


@RestController // Tells Spring this class will be a controller
@RequestMapping("/api/v1/users") // Sets the base path for all endpoints inside the controller
public class UserController {

    @GetMapping("/{id}") // Handles GET requests at /api/v1/users/{id}
    public String getUserById(@PathVariable Long id) {
        return "User with ID: " + id; // Returns a string with the user's ID
    }

    @PostMapping // Handles POST requests to /api/v1/users
    public String createUser(@RequestBody String userName) {
        return "Created user: " + userName; // Returns a message
    }
}
  • @RestController: indicates that returned data will be in JSON or XML format.
  • @RequestMapping: sets the base URL for all handlers in this controller.
  • @GetMapping, @PostMapping etc.: configure handling for specific HTTP methods (GET, POST, DELETE, PUT).
  • @PathVariable: lets you extract parameters from parts of the URL.
  • @RequestBody: reads the request body and converts it from JSON into a Java object.

Building the service layer for business logic

As we established, controllers shouldn't contain business logic — that's the service's job. They act as a bridge between controllers and repositories. Services are created as Spring components using the @Service annotation.

Service example:


@Service // Marks the class as a Spring component
public class UserService {

    private final UserRepository userRepository; // Repository for DB interaction

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUserById(Long id) {
        return userRepository.findById(id) // Get user from the DB
                .orElseThrow(() -> new RuntimeException("User not found!")); // Throw if user not found
    }

    public void createUser(User user) {
        userRepository.save(user); // Save the user to the database
    }
}
  • @Service: tells Spring this is a component that contains business logic.
  • UserRepository: injected automatically via constructor (Dependency Injection, DI).
  • findById and save: standard repository methods for CRUD operations.

Integrating controllers with services

Controllers and services work together. The controller calls service methods to handle requests. We pass the service as a dependency into the controller using DI.

Integration example:


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

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService; // Dependency injection
    }

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id); // Call service method
    }

    @PostMapping
    public void createUser(@RequestBody User user) {
        userService.createUser(user); // Call service method
    }
}

Improving project structure using patterns

To keep your project clear and maintainable, follow these patterns:

  • Controller-Service-Repository: never mix controller and service logic together.
  • DTO (Data Transfer Object): use DTOs to transfer data between layers. This is important when your DB entity structure differs from what the client expects.
  • Separation of responsibilities: controllers handle requests, services implement logic, and repositories work with the database.

Step-by-step practical example

Let's build a simple users feature.

1. Entity:


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

    // Getters and setters...
}

2. Repository:


@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // Additional custom queries are possible
}

3. Service:


@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUserById(Long id) {
        return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public void saveUser(User user) {
        userRepository.save(user);
    }
}

4. Controller:


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

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

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

    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        return ResponseEntity.ok(userService.getAllUsers());
    }

    @PostMapping
    public ResponseEntity<String> createUser(@RequestBody User user) {
        userService.saveUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully");
    }
}

Common mistakes and how to fix them

  1. Mixing logic inside controllers: don't put business logic in controllers. Services are better suited for that.
  2. No error handling: make sure you handle exceptions (for example, with @ExceptionHandler or @ControllerAdvice).
  3. No validation of input: validate request data before using it, e.g., with the @Valid annotation.
  4. Circular dependencies: use DI correctly and avoid mutual references between components.

Now your controllers and services are ready to handle requests, and your app's brain and ears are working nicely. In the next lecture we'll keep building our full-stack, diving into security and authentication with Spring Security!

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION