Introduction to REST API
REST (Representational State Transfer) is an architectural style for building web services. It leverages standard HTTP protocols and is stateless, which means that each HTTP request happens in complete isolation.
URL Naming Conventions in REST API
By following best practices for naming URLs in REST APIs, you can speed up your project’s preparation and make the code easier to read. Here are some recommendations:
-
Use plural nouns: RESTful URLs should use plural nouns when referring to collections. For example,
/orders
instead of/order
. -
Avoid verbs: The HTTP methods should represent the actions, so it’s better to avoid verbs in your URLs. For example,
POST /orders
instead ofPOST /createOrder
. -
Keep it simple and predictable: URLs should be straightforward and consistent to make them easy for the client to understand.
-
Use hyphens (-) to improve readability:
/customer-orders
. -
Use lower case letters: URLs should be case-insensitive, but conventionally, they’re written in lower case.
RESTful API HTTP Methods
When working with REST APIs, different HTTP methods are used to perform actions on the resources. Each method typically requires specific parameters or body content to function correctly.
-
GET: Retrieve a specific resource (by id) or a collection of resources. There’s no body content for GET. The URL itself identifies the resource. You can use request parameters to fine-tune your queries. For example,
GET /orders?status=delivered
might get all the delivered orders. -
POST: This method creates new resources. The information for the new resource (like the details of a new order) should be included in the body of the request, often in JSON format. The URL indicates the collection where the new resource will be added. For example,
POST /orders
. -
PUT: This method updates a resource completely. Similar to POST, the updated details of the resource should be included in the body of the request. The URL should point to the specific resource that needs to be updated. For example,
PUT /orders/{id}
. -
PATCH: This method is used for partial updates. Instead of sending the complete resource, you only send the specific fields that you want to update. For example,
PATCH /orders/{id}
. -
DELETE: This method removes a resource. The URL points to the specific resource to delete. Like GET, DELETE requests usually don’t have a body.
Best Practices
-
Use query parameters for optional parameters. Query parameters also can be used to filter, sort, or paginate collections.
-
Ensure idempotence of PUT and DELETE requests. This means that multiple identical requests should have the same effect as a single request.
-
Use status codes to show how the request played out. For example, “200 OK” means that a GET request was successful, “201 Created” means that a POST request was successful, and “204 No Content” means that a DELETE request was successful.
-
Implement error handling and provide clear error messages.
Java and Spring Boot
Spring Boot makes it simpler for Java developers to build and use REST APIs. Here’s how to use the GET, POST, PUT, and DELETE methods:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
// GET Method
@GetMapping
public ResponseEntity<List<Order>> getAllOrders() {
List<Order> orders = orderService.findAllOrders();
return new ResponseEntity<>(orders, HttpStatus.OK);
}
// POST Method
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
Order newOrder = orderService.createOrder(order);
return new ResponseEntity<>(newOrder, HttpStatus.CREATED);
}
// PUT Method
@PutMapping("/{id}")
public ResponseEntity<Order> updateOrder(@PathVariable Long id, @RequestBody Order order) {
Order updatedOrder = orderService.updateOrder(id, order);
return new ResponseEntity<>(updatedOrder, HttpStatus.OK);
}
// DELETE Method
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteOrder(@PathVariable Long id) {
orderService.deleteOrder(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
In the above example, @RequestMapping("/api/orders")
maps the HTTP operations to the URI /api/orders
. Each method is tagged with the corresponding HTTP method annotation (@GetMapping
, @PostMapping
, @PutMapping
, @DeleteMapping
). The @RequestBody
annotation binds the HTTP request body to the domain object. The @PathVariable
annotation is used to extract the values from the URI.
Handling Unexpected Errors in REST APIs
When designing a REST API, it’s essential to anticipate not just expected exceptions, but also unexpected ones. This means preparing for cases where unanticipated issues (like NullPointerException or database connection errors) may arise.
A good practice is to create a global error handler that catches all types of exceptions. This can be done in Spring Boot using the @ControllerAdvice
annotation. Here’s an example of how you could enhance the Java code above to deal with unexpected errors:
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.WebRequest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
// handling specific exception
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<?> handleOrderNotFound(OrderNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
// handling global exception
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleGlobalException(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
In this code, GlobalExceptionHandler
is a separate class marked with the @ControllerAdvice
annotation, which makes it applicable across all controllers. It has two methods:
handleOrderNotFound()
, which is specific to theOrderNotFoundException
and returns a 404 status.handleGlobalException()
, which catches all other types of Exception and returns a 500 status.
Each method builds a ErrorDetails
object (a custom class that you would need to define) that contains details about the error, and returns it in the response body. This provides useful debug information to the client while keeping your API robust and reliable.