REST API Implementations in .NET and Java
Below are production-ready code snippets illustrating how to build a clean REST API (featuring a resource controller, pagination parameters, proper status codes, and DTO validation) in both .NET (C#) and Java (Spring Boot).
1. .NET implementation (.NET 8 / C# ASP.NET Core)
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
namespace RestApiDemo.Controllers
{
// DTO for incoming user creation requests
public record CreateUserDto(
[Required] string Name,
[EmailAddress][Required] string Email
);
// DTO for user resource representation
public record UserDto(Guid Id, string Name, string Email, DateTime CreatedAt);
// Response wrapper for paginated collections
public record PagedResult<T>(
IEnumerable<T> Items,
int Limit,
int Offset,
int TotalCount
);
[ApiController]
[Route("api/v1/users")]
public class UsersController : ControllerBase
{
// Simulated in-memory database
private static readonly List<UserDto> _users = new();
/// <summary>
/// GET: api/v1/users?limit=10&offset=0
/// Demonstrates query-parameter pagination returning a structured paged payload
/// </summary>
[HttpGet]
public ActionResult<PagedResult<UserDto>> GetUsers(
[FromQuery, Range(1, 100)] int limit = 10,
[FromQuery, Range(0, int.MaxValue)] int offset = 0)
{
var totalCount = _users.Count;
// Apply pagination logic using LINQ Skip and Take
var pagedItems = _users
.Skip(offset)
.Take(limit)
.ToList();
var result = new PagedResult<UserDto>(
Items: pagedItems,
Limit: limit,
Offset: offset,
TotalCount: totalCount
);
return Ok(result);
}
/// <summary>
/// GET: api/v1/users/{id}
/// Demonstrates resource retrieval and 404 Not Found handling
/// </summary>
[HttpGet("{id:guid}")]
public ActionResult<UserDto> GetUserById(Guid id)
{
var user = _users.FirstOrDefault(u => u.Id == id);
if (user == null)
{
return NotFound(new { message = $"User with ID {id} not found." });
}
return Ok(user);
}
/// <summary>
/// POST: api/v1/users
/// Demonstrates resource creation, validation, 201 Created status,
/// and returning a Location header.
/// </summary>
[HttpPost]
public ActionResult<UserDto> CreateUser([FromBody] CreateUserDto createDto)
{
// Check for unique email constraint (simulating 409 Conflict)
if (_users.Any(u => u.Email.Equals(createDto.Email, StringComparison.OrdinalIgnoreCase)))
{
return Conflict(new { message = "A user with this email already exists." });
}
var newUser = new UserDto(Guid.NewGuid(), createDto.Name, createDto.Email, DateTime.UtcNow);
_users.Add(newUser);
// Returns 201 Created with Location header pointing to GET /api/v1/users/{id}
return CreatedAtAction(nameof(GetUserById), new { id = newUser.Id }, newUser);
}
/// <summary>
/// DELETE: api/v1/users/{id}
/// Demonstrates 204 No Content success response
/// </summary>
[HttpDelete("{id:guid}")]
public IActionResult DeleteUser(Guid id)
{
var user = _users.FirstOrDefault(u => u.Id == id);
if (user == null)
{
return NotFound();
}
_users.Remove(user);
return NoContent(); // Standard 204 response for successful deletion
}
}
}2. Java Implementation (Spring Boot 3 / Java)
package com.example.restapi.controller;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
// Records introduced in Java 14+ / fully supported in Spring Boot 3 for DTOs
record CreateUserDto(
@NotBlank String name,
@Email @NotBlank String email
) {}
record UserDto(
UUID id,
String name,
String email,
Instant createdAt
) {}
record PagedResult<T>(
List<T> items,
int limit,
int offset,
int totalCount
) {}
@RestController
@RequestMapping("/api/v1/users")
public class UsersController {
// Simulated in-memory database
private static final List<UserDto> users = new ArrayList<>();
/**
* GET: /api/v1/users?limit=10&offset=0
* Demonstrates pagination query params and 200 OK response.
*/
@GetMapping
public ResponseEntity<PagedResult<UserDto>> getUsers(
@RequestParam(defaultValue = "10") int limit,
@RequestParam(defaultValue = "0") int offset) {
int totalCount = users.size();
List<UserDto> pagedItems = users.stream()
.skip(offset)
.limit(limit)
.collect(Collectors.toList());
PagedResult<UserDto> result = new PagedResult<>(pagedItems, limit, offset, totalCount);
return ResponseEntity.ok(result);
}
/**
* GET: /api/v1/users/{id}
* Demonstrates path variables and 404 Not Found handling.
*/
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUserById(@PathVariable UUID id) {
Optional<UserDto> user = users.stream()
.filter(u -> u.id().equals(id))
.findFirst();
return user.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
/**
* POST: /api/v1/users
* Demonstrates request validation (@Valid), 201 Created status,
* and Location header generation.
*/
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserDto createDto) {
// Check for duplicate unique constraint (simulating 409 Conflict)
boolean emailExists = users.stream()
.anyMatch(u -> u.email().equalsIgnoreCase(createDto.email()));
if (emailExists) {
return ResponseEntity.status(409).body("A user with this email already exists.");
}
UserDto newUser = new UserDto(
UUID.randomUUID(),
createDto.name(),
createDto.email(),
Instant.now()
);
users.add(newUser);
// Build location URI for the newly created resource (Header: Location)
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(newUser.id())
.toUri();
return ResponseEntity.created(location).body(newUser);
}
/**
* DELETE: /api/v1/users/{id}
* Demonstrates 204 No Content success response on deletion.
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable UUID id) {
boolean removed = users.removeIf(u -> u.id().equals(id));
if (!removed) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.noContent().build(); // 204 No Content
}
}