Let's define the RESTful API controller for the country resource. The following is the template for the controller:
@RestController
@RequestMapping("/api/countries")
@Slf4j
public class CountryAPIController {
@Autowired CountryDAO countryDao;
@Autowired WorldBankApiClient worldBankApiClient;
@GetMapping
public ResponseEntity<?> getCountries(
@RequestParam(name="search", required = false) String searchTerm,
@RequestParam(name="continent", required = false) String continent,
@RequestParam(name="region", required = false) String region,
@RequestParam(name="pageNo", required = false) Integer pageNo
){
//logic to fetch contries from CountryDAO
return ResponseEntity.ok();
}
@PostMapping(value = "/{countryCode}",
consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<?> editCountry(
@PathVariable String countryCode, @Valid @RequestBody Country country ){
//logic to edit existing country
return ResponseEntity.ok();
}
@GetMapping("/{countryCode}/gdp")
public ResponseEntity<?> getGDP(@PathVariable String countryCode){
//logic to get GDP by using external client
return ResponseEntity.ok();
}
}
The following are a few things to note from the previous code:
- @RestController: This is used to annotate a class as a controller with each of the RESTful methods returning the data in the response body.
- @RequestMapping: This is for assigning the root URL for accessing the resources.
- @GetMapping and @PostMapping: These are used to assign the HTTP verbs that will be used to invoke the resources. The URL for the resources are passed within the annotation, along with other request headers that consume and produce information.
Let's implement each of the methods in order, starting with getCountries(), as shown in the following code:
@GetMapping
public ResponseEntity<?> getCountries(
@RequestParam(name="search", required = false) String searchTerm,
@RequestParam(name="continent", required = false) String continent,
@RequestParam(name="region", required = false) String region,
@RequestParam(name="pageNo", required = false) Integer pageNo
){
try {
Map<String, Object> params = new HashMap<String, Object>();
params.put("search", searchTerm);
params.put("continent", continent);
params.put("region", region);
if ( pageNo != null ) {
params.put("pageNo", pageNo.toString());
}
List<Country> countries = countryDao.getCountries(params);
Map<String, Object> response = new HashMap<String, Object>();
response.put("list", countries);
response.put("count", countryDao.getCountriesCount(params));
return ResponseEntity.ok(response);
}catch(Exception ex) {
log.error("Error while getting countries", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error while getting countries");
}
}
The following are some of the things to note from the previous code:
- @RequestParam: This annotation is used to declare request parameters accepted by the controller endpoint. The parameters can be provided with a default value and can also be made mandatory.
- ResponseEntity: This class is used to return the response body, along with other response parameters such as status, headers, and so on.
Next up is the API for editing country details, as follows:
@PostMapping("/{countryCode}")
public ResponseEntity<?> editCountry(
@PathVariable String countryCode, @Valid @RequestBody Country country ){
try {
countryDao.editCountryDetail(countryCode, country);
Country countryFromDb = countryDao.getCountryDetail(countryCode);
return ResponseEntity.ok(countryFromDb);
}catch(Exception ex) {
log.error("Error while editing the country: {} with data: {}",
countryCode, country, ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error while editing the country");
}
}
The following are a few things to note from the previous code implementation:
- @PathVariable: This is used to declare any variable that needs to be part of the URL path of the controller endpoint. In our case, we want the country code to be part of the URL. So the URL will be of the /api/countries/IND form.
- @Valid: This triggers the Bean Validation API to check for the restrictions on each of the class properties. If the data from the client is not valid, it returns a 400.
- @RequestBody: This is used to capture the data sent in the request body and the Jackson library is used to convert the JSON data in the request body to the corresponding Java object.
The rest of the API implementation can be found in the CountryAPIController class. The tests for the API controller can be found in the CountryAPIControllerTest class, which is available in the source code of this book.