REST API Quick Start Guide
REST APIs are the universal language of microservices—they use HTTP verbs to manipulate resources and JSON to transfer data, making them simple, standardized, and efficient.
1. What You'll Learn
@RestController/@RequestMapping/@GetMapping/@PostMappingAnnotation System- Path variable
@PathVariable, request parameter@RequestParam, request body@RequestBody - HTTP Status Codes and
ResponseEntityCustom Responses - Testing REST APIs with Postman / curl
- Alice implements the four operations for orders: creation, query, update, and deletion
2. A True Story from an API Developer
(1) Pain Point: Inconsistent API Specifications
Alice joined an existing project team and discovered that the REST API design was chaotic: some methods used GET to modify data, URL naming was inconsistent (/getOrder, /order/list, /deleteOrderById), and the response formats were not standardized—some returned JSON, some returned XML, and error responses were plain text. Bob, a front-end developer, had to consult the back-end team every time he integrated a new API, wasting a significant amount of time on communication.
(2) RESTful Approach
REST uses a set of uniform conventions to define API design:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}") // GET /api/orders/123
@PostMapping // POST /api/orders
@PutMapping("/{id}") // PUT /api/orders/123
@DeleteMapping("/{id}") // DELETE /api/orders/123
}
(3) Revenue
After Alice refactored the OrderFlow API to follow a RESTful style—with consistent URL naming conventions and clearly defined HTTP verb semantics—Bob, a front-end developer, remarked, "You can tell how to use it just by looking at the URL." As a result, the time required to integrate with the API was reduced from an average of 2 hours to 15 minutes.
3. Core Concepts of REST
(1) The Six Principles of REST
REST (Representational State Transfer) defines the core constraints of this architectural style:
graph TD
A[REST Constraints] --> B[Client-Server<br/>Separation of Concerns]
A --> C[Stateless<br/>Each Request Contains<br/>All Needed Info]
A --> D[Cacheable<br/>Responses Define<br/>Cacheability]
A --> E[Uniform Interface<br/>Consistent API Design]
A --> F[Layered System<br/>Client Cannot Tell<br/>If Connected Directly]
A --> G[Code on Demand<br/>Optional: Server Can<br/>Send Executable Code]
(2) Mapping HTTP Verbs to CRUD Operations
| HTTP Verb | Operation | Idempotency | Security | Typical URL |
|---|---|---|---|---|
| GET | Query | Yes | Yes | /api/orders |
| POST | Created | No | No | /api/orders |
| PUT | Full Update | Yes | No | /api/orders/123 |
| PATCH | Partial Update | No | No | /api/orders/123 |
| DELETE | Delete | Yes | No | /api/orders/123 |
4. @RestController and Request Mapping
(1) How @RestController Works
@RestController = @Controller + @ResponseBody indicates that the return values of all methods in this class are written directly into the HTTP response body (JSON by default).
(1) ▶ Example: Basic OrderController
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final List<Map<String, Object>> orders = new ArrayList<>();
@GetMapping
public List<Map<String, Object>> listOrders() {
return orders;
}
@PostMapping
public Map<String, Object> createOrder(
@RequestBody Map<String, Object> order) {
order.put("id", (long) (orders.size() + 1));
order.put("status", "PENDING");
orders.add(order);
return order;
}
}
Output:
// Execution Successful
(2) Request Parameter Binding
(2) ▶ Example: @PathVariable and @RequestParam
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}")
public Map<String, Object> getOrder(@PathVariable Long id) {
return Map.of("id", id, "status", "PENDING");
}
@GetMapping
public List<Map<String, Object>> searchOrders(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String status) {
return List.of(Map.of(
"page", page, "size", size, "status", status
));
}
}
Output:
// Execution Successful
| Comment | Purpose | Example | URL |
|---|---|---|---|
@PathVariable |
Extract variables from the path | @PathVariable Long id |
/api/orders/123 |
@RequestParam |
Extract Query Parameters | @RequestParam String status |
/api/orders?status=PENDING |
@RequestBody |
Extract JSON from request body | @RequestBody OrderRequest req |
POST body |
@RequestHeader |
Extract Request Headers | @RequestHeader String auth |
Header: Authorization |
(3) ▶ Example: Using a DTO to Receive the Request Body
public record CreateOrderRequest(
Long productId,
Integer quantity,
String shippingAddress
) {}
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@PostMapping
public ResponseEntity<Map<String, Object>> createOrder(
@RequestBody CreateOrderRequest request) {
Map<String, Object> order = Map.of(
"productId", request.productId(),
"quantity", request.quantity(),
"shippingAddress", request.shippingAddress(),
"status", "PENDING"
);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(order);
}
}
Output:
// Execution Successful
5. ResponseEntity and HTTP Status Codes
(1) Using ResponseEntity
ResponseEntity Gives you full control over HTTP responses: status codes, response headers, and response bodies.
| Method | Applicable Scenarios | Flexibility |
|---|---|---|
| Returns an object directly | Simple success response | Low (fixed at 200) |
ResponseEntity.ok(body) |
Must be set to 200 | Medium |
ResponseEntity.status(CREATED).body(body) |
Resource creation returned 201 | High |
ResponseEntity.notFound().build() |
Resource not found—returns 404 | High |
(1) ▶ Example: Complete CRUD Operations
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final Map<Long, Map<String, Object>> orderStore = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
@PostMapping
public ResponseEntity<Map<String, Object>> create(
@RequestBody CreateOrderRequest request) {
Long id = idGenerator.getAndIncrement();
Map<String, Object> order = new HashMap<>();
order.put("id", id);
order.put("productId", request.productId());
order.put("quantity", request.quantity());
order.put("status", "PENDING");
orderStore.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getOne(@PathVariable Long id) {
Map<String, Object> order = orderStore.get(id);
if (order == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(order);
}
@PutMapping("/{id}")
public ResponseEntity<Map<String, Object>> update(
@PathVariable Long id,
@RequestBody CreateOrderRequest request) {
if (!orderStore.containsKey(id)) {
return ResponseEntity.notFound().build();
}
Map<String, Object> order = new HashMap<>();
order.put("id", id);
order.put("productId", request.productId());
order.put("quantity", request.quantity());
order.put("status", "CONFIRMED");
orderStore.put(id, order);
return ResponseEntity.ok(order);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
if (orderStore.remove(id) == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.noContent().build();
}
}
Output:
// Execution Successful
6. API Testing Methods
(1) curl test command
(1) ▶ Example: Testing CRUD APIs with curl
# Create order
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"productId":1, "quantity":3, "shippingAddress":"123 Main St"}'
# Get order
curl http://localhost:8080/api/orders/1
# List orders with pagination
curl "http://localhost:8080/api/orders?page=0&size=10"
# Update order
curl -X PUT http://localhost:8080/api/orders/1 \
-H "Content-Type: application/json" \
-d '{"productId":2, "quantity":5, "shippingAddress":"456 Oak Ave"}'
# Delete order
curl -X DELETE http://localhost:8080/api/orders/1
Output:
{"status":"ok","data":{}}
| HTTP Status Code | Meaning | When Returned |
|---|---|---|
| 200 OK | Success | GET / PUT Success |
| 201 Created | Created | POST Resource created successfully |
| 204 No Content | No content | DELETE successful |
| 400 Bad Request | Request Error | Parameter Validation Failed |
| 404 Not Found | Not Found | Resource Does Not Exist |
7. RESTful API Design Specifications
(1) URL Naming Conventions
| Rule | Incorrect Form | Correct Form |
|---|---|---|
| Use of Plural Nouns | /getOrder |
/api/orders |
| Nested Resource Relationships | /orderItems?orderId=1 |
/api/orders/1/items |
| Use Paths Instead of Queries | /api/orders?id=1 |
/api/orders/1 |
| Version Control | None | /api/v1/orders |
| Query Parameters for Filtering | /api/pendingOrders |
/api/orders?status=PENDING |
8. Comprehensive Example: OrderFlow Order CRUD API
package com.orderflow.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public record CreateOrderRequest(
Long productId, Integer quantity, String shippingAddress
) {}
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final Map<Long, Map<String, Object>> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
@PostMapping
public ResponseEntity<Map<String, Object>> create(
@RequestBody CreateOrderRequest req) {
Long id = seq.getAndIncrement();
Map<String, Object> order = new LinkedHashMap<>();
order.put("id", id);
order.put("productId", req.productId());
order.put("quantity", req.quantity());
order.put("shippingAddress", req.shippingAddress());
order.put("status", "PENDING");
order.put("createdAt", java.time.Instant.now());
store.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping
public List<Map<String, Object>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return store.values().stream()
.skip((long) page * size).limit(size).toList();
}
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> get(@PathVariable Long id) {
Map<String, Object> order = store.get(id);
return order != null ? ResponseEntity.ok(order)
: ResponseEntity.notFound().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return store.remove(id) != null
? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
}
}
❓ FAQ
ResponseEntity or just return the object directly?ResponseEntity when you need to set custom status codes (such as 201 or 404) or response headers.application.yml or use @JsonFormat(pattern = "yyyy-MM-dd") on the field./api/v1/orders), which is simple and intuitive. You can also use header-based version control (Accept: application/vnd.orderflow.v1+json), which is more RESTful but more complex to implement.📖 Summary
- REST maps CRUD operations to HTTP verbs: GET for retrieval, POST for creation, PUT for update, and DELETE for deletion
@RestController=@Controller+@ResponseBody, returns JSON@PathVariableretrieves the path variable,@RequestParamretrieves the query parameters, and@RequestBodyretrieves the request bodyResponseEntityFull control over response status codes, headers, and body- RESTful URLs use plural nouns; nesting indicates relationships between resources; query parameters are used for filtering
📝 Exercises
-
Basic Exercise (Difficulty ⭐): Implement CRUD APIs for the Product resource in OrderFlow, including
GET /api/v1/products,POST /api/v1/products,GET /api/v1/products/{id}, andDELETE /api/v1/products/{id}. -
Advanced Problem (Difficulty: ⭐⭐): Add the
PATCH /api/v1/orders/{id}/statusendpoint to the Order API that updates only the order status field. Consider the difference between PATCH and PUT. -
Challenge (Difficulty: ⭐⭐⭐): Implement the
GET /api/v1/orders/{id}/itemsnested resource interface to return all order items under a specified order, with support for pagination parameters.



