Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 55

Building RESTful APIs: CRUD Operations

Recommended resources

Welcome back! In our last lesson, you masterfully set up a layered architecture for our Spring Boot application, separating concerns into Controller, Service, and Repository layers. You now have a clean, professional project structure ready for its core purpose: serving data over the web.

Today, we're going to breathe life into that structure. This lesson focuses on creating your first set of RESTful API endpoints. You will learn how to map standard HTTP methods—GET, POST, PUT, and DELETE—to specific Java methods within your RestController. This is the heart of building a backend API, transforming your application from a standalone program into a service that clients can interact with to create, read, update, and delete resources.

Mapping HTTP Verbs to Controller Methods

In the world of REST APIs, we perform actions on resources using standard HTTP methods, often called "verbs." Each verb has a conventional meaning:

  • GET: Retrieve a resource.
  • POST: Create a new resource.
  • PUT: Update an existing resource completely.
  • DELETE: Remove a resource.

Spring Boot provides a set of powerful and intuitive annotations to map these verbs to your controller methods. This makes the code declarative and easy to read.

These annotations are shortcuts for the more general `@RequestMapping` annotation, specifically tailored for each HTTP method. They are the primary tools you'll use to define your API endpoints.

The general pattern is to create a public method in your @RestController class and annotate it with the corresponding mapping annotation, providing the URL path it should handle.

Let's start building the endpoints for the Employee resource we've been discussing.


Implementing CRUD Endpoints

We will now implement the four core CRUD (Create, Read, Update, Delete) operations. The official Spring guide provides an excellent and clean implementation that we will follow. It builds directly on the layered architecture you've already created.

1. Read Operations (GET)

We typically need two types of GET endpoints: one to retrieve a list of all resources and one to retrieve a single resource by its unique identifier.

  • Get All Resources (GET /employees)
    This endpoint should return a list of all employees. We use the @GetMapping annotation with the path "/employees". The method calls the findAll() method provided by our EmployeeRepository.

    @GetMapping("/employees")
    List<Employee> all() {
        return repository.findAll();
    }
    
  • Get a Single Resource (GET /employees/{id})
    This endpoint retrieves a specific employee. The {id} part in the path is a placeholder. We use the @PathVariable annotation on a method parameter to capture the value from the URL and use it in our code. The method then calls repository.findById(id).

    @GetMapping("/employees/{id}")
    Employee one(@PathVariable Long id) {
        return repository.findById(id)
          .orElseThrow(() -> new EmployeeNotFoundException(id));
    }
    

    Note: The .orElseThrow(...) part is a concise way to handle the case where the ID doesn't exist. We will cover comprehensive exception handling in a later module.

2. Create Operation (POST)

To create a new employee, the client will send an HTTP POST request containing the new employee's data (like name and role) in the request body, typically as a JSON object.

  • Create a New Resource (POST /employees)
    We use the @PostMapping annotation. To access the data from the request body, we use the @RequestBody annotation on a method parameter. Spring automatically deserializes the incoming JSON into an Employee Java object for us. We then simply save this object using the repository.

    @PostMapping("/employees")
    Employee newEmployee(@RequestBody Employee newEmployee) {
        return repository.save(newEmployee);
    }
    

3. Update Operation (PUT)

Updating a resource requires two pieces of information: the ID of the resource to update (from the path) and the new data (from the request body).

  • Update an Existing Resource (PUT /employees/{id})
    This method combines @PathVariable to get the id and @RequestBody to get the updated Employee data. The logic involves finding the existing employee, updating its properties with the new data, and saving it back to the database.

    
    @PutMapping("/employees/{id}")
    Employee replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
        return repository.findById(id)
            .map(employee -> {
                employee.setName(newEmployee.getName());
                employee.setRole(newEmployee.getRole());
                return repository.save(employee);
            })
            .orElseGet(() -> {
                // If not found, some might choose to create it.
                // We will refine this logic later.
                newEmployee.setId(id);
                return repository.save(newEmployee);
            });
    }
    

4. Delete Operation (DELETE)

Deleting a resource is straightforward: we just need the ID of the resource to remove.

  • Delete a Resource (DELETE /employees/{id})
    We use @DeleteMapping and @PathVariable to capture the id from the URL. The method then simply calls repository.deleteById(id). This method typically doesn't need to return any data in the response body.

    @DeleteMapping("/employees/{id}")
    void deleteEmployee(@PathVariable Long id) {
        repository.deleteById(id);
    }
    

To see all these pieces assembled in a complete controller, please review the official Spring guide.

Getting Started | Building REST services with Spring

This official Spring.io guide provides the full code for an EmployeeController that implements all the CRUD operations we've just discussed.

Focus on the EmployeeController class within the section titled HTTP is the Platform. Read through the code and notice how each Java method is annotated to handle a specific HTTP request.


From Theory to Practice: A Video Walkthrough

Reading code is one thing; seeing it built and tested live is another. The following video provides an excellent, step-by-step tutorial for building a complete CRUD application. It reinforces the concepts we've covered and shows you how to test the endpoints using a popular API client called Postman.

CRUD App in 30 mins | Simplest Explanation | Spring Boot | REST | JPA | H2 | Tutorials for Beginners

This video by Prashant Sharma is a fantastic practical guide. It demonstrates implementing each CRUD endpoint and then testing it, which is crucial for verifying your work.

Please watch the following segments to see each endpoint being built and tested: GET Endpoints: Watch this part to see the implementation of GET /books and GET /books/{id}. Notice the use of ResponseEntity, which we will discuss next. POST Endpoint: This segment shows how to create the POST /books endpoint using @RequestBody. PUT Endpoint: Here, the presenter implements the logic for updating a book. DELETE Endpoint: This short clip covers the implementation of the delete functionality. Postman Testing: This is a must-watch. It demonstrates how to use Postman to send requests to all the endpoints you just saw being built, verifying that they work as expected.


Best Practice: Returning Proper HTTP Status Codes

By default, Spring Boot returns an HTTP 200 OK status for successful requests. However, a well-behaved REST API uses more specific status codes to provide richer feedback to the client. For example:

  • 201 Created is more appropriate for a successful POST request.
  • 204 No Content is suitable for a DELETE request that succeeds but doesn't return any data.

Spring's ResponseEntity class gives you full control over the HTTP response, including the status code, headers, and body. Let's refine our POST and DELETE methods.

Refined POST Method:
Instead of returning the Employee object directly, we wrap it in a ResponseEntity and use the .created() builder, which sets the status to 201 Created.

import org.springframework.http.ResponseEntity;

@PostMapping("/employees")
ResponseEntity<Employee> newEmployee(@RequestBody Employee newEmployee) {
    Employee savedEmployee = repository.save(newEmployee);
    // In a more advanced implementation, we would also add a 'Location' header.
    return ResponseEntity.status(HttpStatus.CREATED).body(savedEmployee);
}

Refined DELETE Method:
For the delete operation, we can return a response with no body and a 204 No Content status.

import org.springframework.http.ResponseEntity;

@DeleteMapping("/employees/{id}")
ResponseEntity<?> deleteEmployee(@PathVariable Long id) {
    repository.deleteById(id);
    return ResponseEntity.noContent().build();
}

You can find these improved response patterns in the official Spring guide as well.

Getting Started | Building REST services with Spring

This guide also demonstrates how to use ResponseEntity to return more descriptive HTTP status codes.

Quickly review the updated code for the POST method and the DELETE method in the EmployeeController. Note how ResponseEntity is used to return 201 Created and 204 No Content respectively.


Conclusion

You've made a huge leap forward today! By implementing the four primary RESTful endpoints, you have built the fundamental interface for any modern backend application. This is a core skill that is universally required in software engineering roles.

Key Takeaways:

  • Mapping Annotations: @GetMapping, @PostMapping, @PutMapping, and @DeleteMapping are used to map HTTP requests to controller methods.
  • Handling Request Data: @PathVariable extracts data from the URL path (e.g., /employees/1), while @RequestBody deserializes data from the request body (usually JSON).
  • CRUD Operations: You can now implement the full suite of Create, Read, Update, and Delete functionality for a resource.
  • Meaningful Responses: Using ResponseEntity allows you to control the HTTP status code and provide more accurate feedback to API clients, which is a hallmark of professional development.
  • API Testing: Tools like Postman are essential for testing your backend endpoints independently of a frontend application.

In the next lesson, we will take a closer look at handling incoming data. We'll explore the differences between @PathVariable and @RequestParam, handle optional parameters, and see how Spring's data binding works in more detail.