Web APIs has become a very important topic. Typically we use a RESTful design for them. The concept of REST is to separate the API structure into logical resources.
There are a few best practices which are for designing a clean RESTful API. Some of them will be used for the purpose of this blog.
Using nouns for url naming convention is strongly preferred
Use only plural nouns – /cars instead of /car
Sub – resources for relations
/api/cars – returns a list of all cars
/api/cars/4 – returns a car with id=4
Provide filtering and sorting for collections
Filtering – /cars?car_engine=V12 – returns a list of cars with that engine
Sorting – /cars?sort=horsepower
Use HTTP status codes
Version your API – /v1/api/
Martin Fowler blog was used as a reference.
Service overview and requirements:
Store new car object into data base
Change parameters for current existing car
Delete existing car
Retrieve a car from data base by id
Retrieve a list of all cars stored in data base
The source code can be found on my GitHub profile.
Work flow
First I will list the anchor points for developing this tutorial and then all of them will be explained in same order.
Create DB
Add project dependencies
Add a main method which will be used for project execution
Create DAO
Add Service layer
Short explanation and example for adding resource for the implementation of our REST endpoint
Let’s start !
1. Setting up the data base
CREATE TABLE muscle_cars ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, car_brand VARCHAR(40) NOT NULL, car_model VARCHAR(40) NOT NULL, horsepower INT NOT NULL, car_engine VARCHAR(50) NOT NULL, ) insert into muscle_cars (car_brand, car_model, horsepower, car_engine) values ('Plymouth', 'GTX 440 Six Pack', 375, 'V8'), ('Ford', 'Mustang 428 Super Cobra Jet', 375, 'V8'), ('Plymouth', 'Superbird 426 Hemi', 425, 'V6'), ('Dodge', 'Challenger R/T 426 Hemi', 450, 'V12')
2. Create project
2.1 Maven
These are the dependencies which are need for this tuorial. The complete code can be found in GitHub repository.
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> </dependencies>
2.2 Add main method
The execution of the project will start when fire up this main method.
@SpringBootApplication( scanBasePackages = "com.scar") @Import({DataSourceConfig.class}) public class CarsApplication { public static void main(String[] args) { SpringApplication.run(CarsApplication.class, args); } }
The @SpringBootApplication annotation is equivalent to using @Configuration, @EnableAutoConfiguration, @ComponentScan with their default attributes.
3. Setting up the DAO
For the purpose of this tutorial I will use the abstract class which Spring provides – JdbcDaoSupport. By extending JdbcDaoSupport , set the datasource and Jdbctemplate in your class is no longer required, you just need to inject the correct datasource into MuscleCarDaoImpl. And you can get JdbcTemplate by using a getJdbcTemplate() method.
@Repository public class MuscleCarDaoImpl extends JdbcDaoSupport implements MuscleCarDao { @Autowired public MuscleCarDaoImpl(JdbcTemplate jdbcTemplate, DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); setJdbcTemplate(jdbcTemplate); } @Override public MuscleCar getCarFromList(int id) { String sql = "select * from muscle_cars where id = ?"; Object[] args = new Object[] { id }; return getJdbcTemplate().queryForObject(sql, args, new MuscleCarRowMapper()); } @Override public void removeCarFromList(int id) { String sql = "delete from muscle_cars where id = ?"; Object[] args = new Object[] { id }; getJdbcTemplate().update(sql, args); } @Override public void addCarToList(MuscleCar muscleCar) { String sql = "insert into muscle_cars (car_brand, car_model, horsepower, car_engine) values (?, ?, ?, ?)"; Object[] args = new Object[] {muscleCar.getCarBrand(), muscleCar.getCarModel(), muscleCar.getHorsepower(), muscleCar.getCarEngine()}; getJdbcTemplate().update(sql, args); } @Override public void updateCarFromList(int id, MuscleCar muscleCar) { String sql = "update muscle_cars set car_brand = ?, car_model = ?, horsepower = ?, car_engine = ? where id =?"; Object[] args = new Object[] {muscleCar.getCarBrand(), muscleCar.getCarModel(), muscleCar.getHorsepower(), muscleCar.getCarEngine(), id}; getJdbcTemplate().update(sql, args); } @Override public List<Map<String, Object>> listAllCars() { String sql = "select * from muscle_cars"; return getJdbcTemplate().queryForList(sql); } }
4. Adding a Service layer
The service will do some basic verifications for the data which we will retrieve. If a layer like this is need in a project which you are working on the verifications must be more detailed.
@Service public class MuscleCarService { @Autowired private MuscleCarDao muscleCarDao; public MuscleCar getCar(int id) throws ValidateException { if (id <= 0) { throw new ValidateException("ID can not be 0 or <0"); } return muscleCarDao.getCarFromList(id); } public void removeCarFromList(int id) throws ValidateException { if (id <= 0) { throw new ValidateException("ID can not be 0 or < 0 or this id do not exist"); } muscleCarDao.removeCarFromList(id); } public List<Map<String, Object>> listAllCars() { List<Map<String, Object>> result = muscleCarDao.listAllCars(); if (!result.isEmpty()) { return result; } else { return null; } } public void addCarToList(MuscleCar muscleCar) throws MuscleCarException { if (muscleCar == null) { throw new MuscleCarException("The passed object can not be null."); } muscleCarDao.addCarToList(muscleCar); } public void updateCarFromList(int id, MuscleCar muscleCar) { if ( id <= 0 && muscleCar == null) { throw new IllegalArgumentException("The passed object can not be null."); } muscleCarDao.updateCarFromList(id, muscleCar); } }
5. Adding a Resource
Next up, we have the implementation of our REST endpoint. We will use this class to map our service to incoming HTTP requests.
The @RestController is a convenience annotation between @Controller and @ResponseBody and marks this class as a web component discovered during class path scanning. The @RequestMapping annotation at the class level defines the base path mapping used for any other @RequestMapping annotations in this class. In this case all endpoints will start with path – /api/muscle.
The @RequestMapping annotation has many options and with this tutorial we are using small part.
value=”/url/{id}” used in conjunction with @PathVariable(“id”) int id maps the {id} part of the url path to the given method argument.
method = RequestMethod.GET/POST/PUT/DELETE define the HTTP method that is accepted
Method parameters annotated with @RequestBody will be populated with the incoming request JSON data.
@RestController @RequestMapping(value = "/v1/api") public class MuscleCarResource { @Autowired private MuscleCarService muscleCarService; @RequestMapping(value = "/cars", method = RequestMethod.GET) public ResponseEntity<List<Map<String, Object>>> listAllCars() { try { List<Map<String, Object>> result = muscleCarService.listAllCars(); return ResponseEntity.status(HttpStatus.OK).body(result); } catch (IllegalStateException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } @RequestMapping(value = "/cars/{id}", method = RequestMethod.GET) public ResponseEntity<MuscleCar> getMuscleCar(@PathVariable("id") int id) { try { MuscleCar muscleCar = muscleCarService.getCar(id); if (muscleCar != null) { return ResponseEntity.status(HttpStatus.OK).body(muscleCar); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } } catch (ValidateException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } @RequestMapping(value = "/cars/delete/{id}", method = RequestMethod.DELETE) public String deleteMuscleCar(@PathVariable("id") int id) { try { muscleCarService.removeCarFromList(id); return String.valueOf(ResponseEntity.status(HttpStatus.OK)); } catch (ValidateException e) { return String.valueOf(ResponseEntity.status(HttpStatus.BAD_REQUEST)); } } @RequestMapping(value = "/cars/add", method = RequestMethod.POST) public ResponseEntity<Void> addCarToList( @RequestBody MuscleCar muscleCar) { try { muscleCarService.addCarToList(muscleCar); return ResponseEntity.status(HttpStatus.OK).build(); } catch (MuscleCarException e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } @RequestMapping(value = "/cars/update/{id}", method = RequestMethod.PUT) public ResponseEntity<Void> updateCar(@PathVariable("id") int id, @RequestBody MuscleCar muscleCar) { try { muscleCarService.updateCarFromList(id, muscleCar); return ResponseEntity.status(HttpStatus.OK).build(); } catch(IllegalStateException e ) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } }
That’s it. Run the application and see the results at https://localhost:8080
For easy testing can be used a tool like SoapUI or some browser plug-in like Postman.