Introduction to REST API URL Design
Best practices for designing clean, intuitive, and RESTful URLs for your APIs
9 minute read
REST (Representational State Transfer) is an architectural style for designing networked applications, first described by Roy Fielding in his 2000 doctoral dissertation. Rather than being a protocol like SOAP or a specific technology, REST is a set of constraints and principles that, when applied to a system’s design, emphasize scalability, simplicity, modifiability, portability, and reliability.
This section explores the core concepts that define RESTful architecture and provides best practices for implementing RESTful APIs that adhere to these principles.
REST is built upon the Hypertext Transfer Protocol (HTTP), leveraging its well-established infrastructure, methods, status codes, and header semantics. This foundation provides several advantages:
Statelessness: Each request from client to server must contain all information needed to understand and complete the request, with no client context stored on the server between requests.
Client-Server Architecture: Separation of concerns between client and server improves portability across platforms and scalability by simplifying server components.
Cacheability: Responses must define themselves as cacheable or non-cacheable to prevent clients from reusing stale or inappropriate data.
Layered System: The architecture permits hierarchical layers, allowing for load balancing, shared caches, and security policies.
REST APIs use standard HTTP methods to perform operations on resources:
Method | Purpose | Example Usage |
---|---|---|
GET | Retrieve resources | GET /api/products/12 |
POST | Create resources | POST /api/products |
PUT | Replace resources | PUT /api/products/12 |
PATCH | Partially update resources | PATCH /api/products/12 |
DELETE | Remove resources | DELETE /api/products/12 |
HEAD | Retrieve headers only | HEAD /api/products/12 |
OPTIONS | Determine available operations | OPTIONS /api/products |
// Controller implementing RESTful HTTP methods
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _productRepository;
public ProductsController(IProductRepository productRepository)
{
_productRepository = productRepository;
}
// GET: api/products
[HttpGet]
public ActionResult<IEnumerable<ProductDto>> GetProducts()
{
IEnumerable<ProductDto> products = _productRepository.GetAll();
return Ok(products);
}
// POST: api/products
[HttpPost]
public ActionResult<ProductDto> CreateProduct(CreateProductDto productDto)
{
ProductDto newProduct = _productRepository.Create(productDto);
return CreatedAtAction(nameof(GetProduct), new { id = newProduct.Id }, newProduct);
}
// GET: api/products/{id}
[HttpGet("{id}")]
public ActionResult<ProductDto> GetProduct(int id)
{
ProductDto product = _productRepository.GetById(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
HTTP’s widespread adoption ensures maximum compatibility across different systems, programming languages, and environments. This technology-independent foundation enables secure and reliable data transfer between diverse platforms, making REST APIs accessible from virtually any programming environment.
URLs (Uniform Resource Locators) form the backbone of any RESTful API, serving as the primary interface between clients and your API. A well-designed URL structure creates an intuitive, discoverable API that developers can quickly understand and integrate with.
The cornerstone of RESTful URL design is resource orientation. Resources are the nouns of your API, while HTTP methods represent the verbs that act upon those resources. This separation creates a clear, consistent interface:
# Collection resource
/api/products
# Specific resource
/api/products/1234
# Nested resources
/api/customers/42/orders
/api/customers/42/orders/789
Nouns, Not Verbs: URLs should identify resources, not actions
/api/orders/123
/api/getOrder/123
Plural Resource Names: Use plural nouns for collections
/api/products
/api/product
Hierarchical Relationships: Express ownership through URL structure
/api/departments/42/employees
- All employees in department 42/api/departments/42/employees/789
- Employee 789 in department 42Consistent Casing: Use kebab-case for multi-word resources
/api/shipping-addresses
/api/shippingAddresses
or /api/shipping_addresses
Query Parameters for Filtering, Sorting, and Pagination
/api/products?category=electronics
- Filtering/api/products?sort=price:asc
- Sorting/api/products?page=2&pageSize=10
- Pagination[ApiController]
[Route("api/departments")]
public class DepartmentsController : ControllerBase
{
private readonly IDepartmentRepository _departmentRepository;
private readonly IEmployeeRepository _employeeRepository;
public DepartmentsController(
IDepartmentRepository departmentRepository,
IEmployeeRepository employeeRepository)
{
_departmentRepository = departmentRepository;
_employeeRepository = employeeRepository;
}
// GET: api/departments/{id}/employees
[HttpGet("{departmentId}/employees")]
public ActionResult<IEnumerable<EmployeeDto>> GetDepartmentEmployees(int departmentId)
{
if (!_departmentRepository.Exists(departmentId))
{
return NotFound("Department not found");
}
IEnumerable<EmployeeDto> employees = _employeeRepository.GetByDepartmentId(departmentId);
return Ok(employees);
}
}
URLs represent a contract between your API and its consumers. A well-structured URL architecture improves discoverability, creates a more intuitive API, and reduces integration complexity.
For detailed guidelines on REST URL design principles and patterns, see the URL Design page.
REST APIs are format-agnostic, giving developers flexibility to choose the most appropriate data representation for their specific use case. While REST itself doesn’t mandate a specific format, the response format should be plaintext-based for maximum interoperability and ease of use.
JSON (JavaScript Object Notation)
{
"id": 42,
"name": "Product Name",
"price": 29.99,
"inStock": true,
"category": "electronics",
"tags": ["new", "featured"]
}
XML (eXtensible Markup Language)
<product>
<id>42</id>
<name>Product Name</name>
<price>29.99</price>
<inStock>true</inStock>
<category>electronics</category>
<tags>
<tag>new</tag>
<tag>featured</tag>
</tags>
</product>
Other Formats
Many RESTful APIs support multiple formats through HTTP content negotiation, allowing clients to request their preferred format:
# Client requests JSON response
GET /api/products/42
Accept: application/json
# Client requests XML response
GET /api/products/42
Accept: application/xml
[ApiController]
[Route("api/products")]
[Produces("application/json", "application/xml")]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
// GET: api/products/42
[HttpGet("{id}")]
public ActionResult<ProductDto> GetProduct(int id)
{
ProductDto product = _repository.GetById(id);
if (product == null)
{
return NotFound();
}
// The response format will be determined by the Accept header
return Ok(product);
}
}
While REST is format-agnostic, choosing the appropriate format for your API is an important design decision that affects usability, performance, and compatibility. Most modern APIs use JSON as the default format due to its simplicity, readability, and ubiquity.
For detailed information on REST data formats, content negotiation, and implementation best practices, see the REST Format page.
Not all APIs calling themselves “RESTful” adhere to the same level of REST constraints. The Richardson Maturity Model, developed by Leonard Richardson, classifies REST APIs into four levels of maturity (0-3):
Level 0: The Swamp of POX (Plain Old XML)
Level 1: Resources
/api/customers/42
but still sending commands via POSTLevel 2: HTTP Verbs
Level 3: Hypermedia Controls (HATEOAS)
A Level 3 REST API (implementing HATEOAS) would return not just data but also links to related resources:
{
"id": 42,
"name": "John Doe",
"email": "[email protected]",
"_links": {
"self": { "href": "/api/customers/42" },
"orders": { "href": "/api/customers/42/orders" },
"address": { "href": "/api/customers/42/address" }
}
}
For a detailed explanation of REST API maturity levels and implementation examples at each level, see the Maturity Levels page.
To be truly RESTful, an API should adhere to the following six architectural constraints defined by Roy Fielding:
The separation of concerns between client and server allows each to evolve independently:
Each request from client to server must contain all information needed to understand and complete the request:
// Stateless API endpoint - all required information is in the request
[HttpGet("products")]
public ActionResult<IEnumerable<ProductDto>> GetProducts(
[FromQuery] string category = null,
[FromQuery] decimal? minPrice = null,
[FromQuery] decimal? maxPrice = null,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10)
{
// Process request using only the provided parameters
// No dependency on previous requests or stored client state
PagedResult<ProductDto> result = _productService.GetFilteredProducts(
new ProductFilterCriteria
{
Category = category,
MinPrice = minPrice,
MaxPrice = maxPrice
},
page,
pageSize);
return Ok(result);
}
Responses must explicitly indicate whether they can be cached:
[HttpGet("products/{id}")]
[ResponseCache(Duration = 60)] // Cache for 60 seconds
public ActionResult<ProductDto> GetProduct(int id)
{
ProductDto product = _repository.GetById(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
A client cannot ordinarily tell whether it is connected directly to the end server or an intermediary:
The uniform interface constraint is fundamental to RESTful design:
Servers can temporarily extend client functionality by transferring executable code:
REST provides a powerful architectural style for designing networked applications that emphasize scalability, simplicity, and interoperability. By leveraging the HTTP protocol, adopting resource-oriented URL design, and supporting flexible data formats, REST APIs offer a standardized approach to creating web services that are easy to understand, implement, and maintain.
The concepts covered in this section—HTTP methods, URL design, data formats, and REST constraints—form the foundation of RESTful API design. By following these principles and best practices, developers can create APIs that are intuitive, discoverable, and provide an excellent developer experience.
To explore these concepts further, including detailed implementation guidelines and examples, see the following pages:
Best practices for designing clean, intuitive, and RESTful URLs for your APIs
Comprehensive guide to data formats used in REST APIs, including JSON, XML, and binary formats
Understanding the Richardson Maturity Model and how to evolve your REST APIs to higher maturity levels