3 Post
djmil edited this page 2023-07-25 12:14:11 +02:00

Test the HTTP Post Endpoint

Edit src/test/java/example/cashcard/CashCardApplicationTests.java and add the following test method.

import java.net.URI;
...

@Test
void shouldCreateANewCashCard() {
	// The database will create and manage all unique `CashCard.id` values for us. We should not provide one.
	CashCard newCashCard = new CashCard(null, 250.00);

	// Post request MUST provide newCashCard data
	// We expect nothing (Void) in the Post.Response.Body
	ResponseEntity<Void> createResponse = restTemplate.postForEntity("/cashcards", newCashCard, Void.class );

	assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);

	// Get Location Header of newly created respurce and use it for Get request
	URI locationOfNewCashCard = createResponse.getHeaders().getLocation();
	ResponseEntity<String> getResponse = restTemplate.getForEntity(locationOfNewCashCard, String.class);
	assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);

	// Validate Get response - a JSON for newly created CashCard entity 
	DocumentContext documentContext = JsonPath.parse(getResponse.getBody());
	Number id = documentContext.read("$.id");
	Double amount = documentContext.read("$.amount");

	assertThat(id).isNotNull();
	assertThat(amount).isEqualTo(250.00);
}

Location Header

The official spec continue to state the following:

send a 201 (Created) response containing a Location header field that provides an identifier for the primary resource created ...

In other words, when a POST request results in the successful creation of a resource, such as a new CashCard, the response should include information for how to retrieve that resource. We'll do this by supplying a URI in a Response Header named "Location".

URI locationOfNewCashCard = createResponse.getHeaders().getLocation();

Note that URI is indeed the correct entity here and not a URL; a URL is a type of URI, while a URI is more generic.

Simple endpoint stub

The POST endpoint is similar to the GET endpoint in our CashCardController, but uses the @PostMapping annotation from Spring Web. The POST endpoint must accept the data we are submitting for our new CashCard, specifically the amount. But what happens if we don't accept the CashCard?

Edit src/main/java/example/cashcard/CashCardController.java and add the following method.

import org.springframework.web.bind.annotation.PostMapping;
...

@PostMapping
private ResponseEntity createCashCard() {
   return null;
}

Note that by returning nothing at all, Spring Web will automatically generate an HTTP Response Status code of 200 OK. But, this isn't very satisfying -- our POST endpoint does nothing! So let's make our tests better.

Actual implementation

import org.springframework.web.util.UriComponentsBuilder;
...

@PostMapping
private ResponseEntity<Void> createCashCard(@RequestBody CashCard newCashCardRequest, UriComponentsBuilder ucb) {
	// CrudRepository.save - Create
	CashCard savedCashCard = cashCardRepository.save(newCashCardRequest);

	URI locationOfNewCashCard = ucb
            .path("cashcards/{id}")
            .buildAndExpand(savedCashCard.id())
            .toUri();

	return ResponseEntity.created(locationOfNewCashCard).build();
}

CrudRepository.save

CashCard savedCashCard = cashCardRepository.save(newCashCardRequest);

CrudRepository provides methods that support creating, reading, updating, and deleting data from a data store. cashCardRepository.save(newCashCardRequest) does just as it says: it saves a new CashCard for us, and returns the saved object with a unique id provided by the database. Amazing!

Post request, get the body

createCashCard(@RequestBody CashCard newCashCardRequest, ...)

Unlike the GET we added earlier, the POST expects a request "body". This contains the data submitted to the API. Spring Web will deserialize the data into a CashCard for us.

Location Header

URI locationOfNewCashCard = ucb
   .path("cashcards/{id}")
   .buildAndExpand(savedCashCard.id())
   .toUri();

This is constructing a URI to the newly created CashCard. This is the URI that the caller can then use to GET the newly-created CashCard. Note that savedCashCard.id is used as the identifier, which matches the GET endpoint's specification of cashcards/<CashCard.id>.

Where did UriComponentsBuilder come from?

We were able to add UriComponentsBuilder ucb as a method argument to this POST handler method and it was automatically passed in. How so? It was injected from our now-familiar friend, Spring's IoC Container. Thanks, Spring Web!

Security

It is pretty obvious, that the system should have some way of handling data from different users. The Home#Security section is a good place to continue reading. You can continue explore other CRUD methods afterwards.