package example.micronaut;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
@RestController (1)
@RequestMapping("/subscriptions") (2)
public class SaasSubscriptionPostController {
private final SaasSubscriptionRepository repository;
private SaasSubscriptionPostController(SaasSubscriptionRepository repository) { (3)
this.repository = repository;
}
@PostMapping (4)
private ResponseEntity<Void> createSaasSubscription(@RequestBody SaasSubscription newSaasSubscription, (5)
UriComponentsBuilder ucb) {
SaasSubscription subscription = repository.save(newSaasSubscription);
URI locationOfSubscription = ucb
.path("subscriptions/{id}")
.buildAndExpand(subscription.id())
.toUri();
return ResponseEntity.created(locationOfSubscription).build();
}
}
3. POST - Spring Boot vs Micronaut Framework - Building a Rest API
This guide compares how to add POST endpoint in both Micronaut and Spring Boot applications.
Authors: Sergio del Amo
Micronaut Version: 4.6.3
1. Sample Project
You can download a sample application with the code examples in this article.
2. Introduction
This guide compares how to write a POST endpoint backed by a persistence layer in a Micronaut Framework and Spring Boot applications.
The Spring Boot application uses Spring Data and H2. The Micronaut application uses Micronaut Data and H2. Micronaut Data is a database access toolkit that uses Ahead of Time (AoT) compilation to pre-compute queries for repository interfaces that are then executed by a thin, lightweight runtime layer.
This guide is the fourth tutorial of Building a Rest API - a series of tutorials comparing how to develop a REST API with Micronaut Framework and Spring Boot.
3. Controller
The controller receives a POST request with a JSON body, the repository persists the SaasSubscription
, and then it returns
a 201 response with a Location
HTTP Header.
As you will see, both controllers are almost identical, with similar annotations, as we discussed in the second guide of this series.
The new APIs used in the following controllers are similar:
Spring Boot | Micronaut | |
---|---|---|
Identify a method as a POST endpoint |
|
|
Read and deserialize the request body to a method parameter |
|
|
Fluid ApI to build a URI |
|
|
3.1. Spring Boot Controller
1 | Annotate a class @RestController to identify the class as a Component capable of handling HTTP requests. |
2 | @RequestMapping identifies the request paths that invoke this Controller. |
3 | Use constructor injection to inject a bean of type SaasSubscriptionRepository . |
4 | The @PostMapping annotation maps the createSaasSubscription method to an HTTP POST request on /subscriptions . |
5 | You can use the @RequestBody annotation to have the request body read and deserialized into an Object. |
3.2. Micronaut Controller
package example.micronaut;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.uri.UriBuilder;
import java.net.URI;
@Controller("/subscriptions") (1)
class SaasSubscriptionPostController {
private final SaasSubscriptionRepository repository;
SaasSubscriptionPostController(SaasSubscriptionRepository repository) { (2)
this.repository = repository;
}
@Post (3)
HttpResponse<?> createSaasSubscription(@Body SaasSubscription newSaasSubscription) { (4)
SaasSubscription savedSaasSubscription = repository.save(newSaasSubscription);
URI locationOfNewSaasSubscription = UriBuilder.of("/subscriptions") (5)
.path(savedSaasSubscription.id().toString())
.build();
return HttpResponse.created(locationOfNewSaasSubscription);
}
}
1 | The class is defined as a controller with the @Controller annotation mapped to the path /subscriptions . |
2 | Use constructor injection to inject a bean of type SaasSubscriptionRepository . |
3 | The @Post annotation maps the createSaasSubscription method to an HTTP POST request on /subscriptions . |
4 | The @Body annotation indicates that the request body should be parsed by Micronaut Framework and bound to the annotated parameter. |
5 | UriBuilder API allows you to create a java.net.URI easily. |
4. Tests
In this tutorial, we use AssertJ in the tests. Moreover, we use Jayway JsonPath - a Java DSL for reading JSON documents.
The test saves a subscription and then uses the response Location
header to fetch the saved subscription. It then compares if the GET response matches what we sent in the POST request.
4.1. Spring Boot Test
The following test shows that retrieving the status code or the location header from a ResponseEntity
is easy.
package example.micronaut;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) (1)
class SaasSubscriptionPostControllerTest {
@Autowired (2)
TestRestTemplate restTemplate; (3)
@Test
void shouldCreateANewSaasSubscription() {
SaasSubscription newSaasSubscription = new SaasSubscription(null, "Advanced", 2500);
ResponseEntity<Void> createResponse = restTemplate.postForEntity("/subscriptions", newSaasSubscription, Void.class);
assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);
URI locationOfNewSaasSubscription = createResponse.getHeaders().getLocation();
ResponseEntity<String> getResponse = restTemplate.getForEntity(locationOfNewSaasSubscription, String.class);
assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
DocumentContext documentContext = JsonPath.parse(getResponse.getBody());
Number id = documentContext.read("$.id");
assertThat(id).isNotNull();
Integer cents = documentContext.read("$.cents");
assertThat(cents).isEqualTo(2500);
}
}
1 | The @SpringBootTest annotation tells Spring Boot to look for a main configuration class (one with @SpringBootApplication , for instance) and use that to start a Spring application context. |
2 | Inject a bean of type TestRestTemplate by using @Autowired on the field definition. |
3 | TestRestTemplate is a helper to ease to execution of HTTP requests against the locally running application. |
4.2. Micronaut Test
The following test shows that retrieving the status code or the location header from a HttpResponse
is easy.
package example.micronaut;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import java.net.URI;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
@MicronautTest (1)
class SaasSubscriptionPostControllerTest {
@Test
void shouldCreateANewSaasSubscription(@Client("/") HttpClient httpClient) { (2)
BlockingHttpClient client = httpClient.toBlocking();
SaasSubscription subscription = new SaasSubscription(100L, "Advanced", 2900);
HttpResponse<Void> createResponse = client.exchange(HttpRequest.POST("/subscriptions", subscription), Void.class);
assertThat(createResponse.getStatus().getCode()).isEqualTo(HttpStatus.CREATED.getCode());
Optional<URI> locationOfNewSaasSubscriptionOptional = createResponse.getHeaders().get(HttpHeaders.LOCATION, URI.class);
assertThat(locationOfNewSaasSubscriptionOptional).isPresent();
URI locationOfNewSaasSubscription = locationOfNewSaasSubscriptionOptional.get();
HttpResponse<String> getResponse = client.exchange(HttpRequest.GET(locationOfNewSaasSubscription), String.class);
assertThat(getResponse.getStatus().getCode()).isEqualTo(HttpStatus.OK.getCode());
DocumentContext documentContext = JsonPath.parse(getResponse.body());
Number id = documentContext.read("$.id");
assertThat(id).isNotNull();
Integer cents = documentContext.read("$.cents");
assertThat(cents).isEqualTo(2900);
}
}
1 | Annotate the class with @MicronautTest so the Micronaut framework will initialize the application context and the embedded server. More info. |
2 | Inject the HttpClient bean and point it to the embedded server. |
5. Conclusion
As you see in this guide, the code to write a POST endpoint backed by a persistence layer in Micronaut Framework and Spring Boot applications is very similar. However, the Micronaut compile-time approach does not require reflection or proxies, leading to faster and more efficient applications.
6. License
All guides are released with an Apache license 2.0 license for the code and a Creative Commons Attribution 4.0 license for the writing and media (images…). |