7.4. RESTful URL Design
Table of Contents
Principles of RESTful URL Design
Designing URLs is not only about making routes work. Good RESTful URL design makes your API predictable, easy to understand, and easy to use. In this chapter you will learn how to design clear, consistent URLs that follow common REST practices.
You will see many examples and small counterexamples so you can develop good instincts when you design your own APIs later.
Resources, Not Actions
In REST, you design URLs around resources, not actions or operations.
A resource is usually a thing in your system, often a noun.
Examples of resources:
usersordersproductspostscomments
A very common mistake is to put verbs in URLs, like this:
POST /createUser
POST /addNewUser
GET /getUserById
POST /banUserThis makes the URL describe the action, but in REST the HTTP method already describes the action.
Instead, make the URL describe the resource and let the HTTP method describe what you want to do with it.
Better:
POST /users # create a user
GET /users/{id} # get a user
DELETE /users/{id} # delete (or ban) a userYou will learn in the next chapters how HTTP methods map to actions, but for now remember this:
Rule: Use nouns in URLs and use HTTP methods (GET, POST, PUT, PATCH, DELETE) to describe actions.
Another example, for blog posts:
Bad:
POST /createPost
GET /listPosts
POST /publishPost/{id}Better:
POST /posts # create post
GET /posts # list posts
GET /posts/{id} # get single post
POST /posts/{id}/publish # perform a specific operation on a resourcePlural Nouns and Resource Collections
URLs for resources that represent a collection are usually plural nouns.
Common patterns:
| Action | Method | URL |
|---|---|---|
| List all users | GET | /users |
| Create a new user | POST | /users |
| Get one user | GET | /users/{userId} |
| Update one user (full) | PUT | /users/{userId} |
| Update one user (partial) | PATCH | /users/{userId} |
| Delete one user | DELETE | /users/{userId} |
Why plural?
/usersclearly means "collection of users"./users/123clearly means "one user from that collection".
You will sometimes see singular resources, like:
/config/profile/status
These are usually singleton resources. For example, every authenticated user has exactly one profile.
Good patterns:
GET /users # collection of users
GET /users/42 # single user
GET /profile # the current user's profile (singleton)
GET /status # global system status (singleton)Avoid mixing singular and plural names for the same resource:
# Bad
GET /user # lists users
GET /users # also lists users
GET /user/{id} # same as /users/{id}Pick one convention and stay consistent across the API. Consistency is more important than which style you pick.
Path Parameters for Resource Identity
You often need to refer to a specific resource. In RESTful URLs you usually do this with path parameters.
A path parameter is a variable part of the URL path that identifies a specific resource.
Examples:
/users/{userId}
/orders/{orderId}
/products/{productId}
Here {userId}, {orderId}, and {productId} are placeholders. A real request might look like:
GET /users/123
GET /orders/2024-0001
GET /products/iphone-15-proThe important part is what goes where:
- Path parameters identify a specific resource.
- Query parameters (covered in a later chapter) are for filtering, sorting, or optional parameters.
So, to get a specific user, you should use a path parameter.
Good:
GET /users/123Less RESTful:
GET /users?id=123Both might work technically, but the first one communicates better that this is a single resource, not a list of users.
Path parameters can be anything that uniquely identifies a resource, for example:
GET /users/john.doe
GET /users/jane@example.com
GET /posts/2024/08/hello-worldYou will later decide in the "Database" and "Authentication" chapters what identifiers make sense, but the URL design idea stays the same.
Hierarchies and Subresources
Sometimes a resource is naturally contained inside another resource. For example:
- Comments belong to posts.
- Line items belong to orders.
- Addresses belong to users.
In those cases it can make sense to represent that relationship in the URL path, as a subresource.
Examples:
GET /posts/{postId}/comments # list comments for a post
POST /posts/{postId}/comments # add a new comment to a post
GET /posts/{postId}/comments/{id} # get a specific comment of a postFor orders and items:
GET /orders/{orderId}/items # list items in an order
POST /orders/{orderId}/items # add a line item
GET /orders/{orderId}/items/{itemId} # get one itemOr for users and addresses:
GET /users/{userId}/addresses
POST /users/{userId}/addresses
GET /users/{userId}/addresses/{addressId}Subresources are very common and make sense when:
- The subresource cannot exist without the parent resource (comments without post usually make no sense).
- The subresource always belongs to exactly one parent.
However, avoid going too deep in nesting.
Bad:
GET /users/{userId}/orders/{orderId}/items/{itemId}/reviews/{reviewId}This becomes long and hard to work with. A simple rule of thumb:
Rule: Try to keep nesting to at most two levels, like /parents/{id}/children/{childId}.
If deeper relationships are needed, you can often flatten them:
GET /order-items/{itemId}
GET /reviews/{reviewId}Using Query Parameters for Filtering and Pagination
URLs often need extra information such as filters, search terms, or pagination. This is where query parameters are usually used.
The general form:
GET /resource?key=value&key2=value2A few common examples:
Filtering
GET /products?category=phones
GET /products?category=phones&brand=apple
GET /users?role=admin&active=trueThis says:
- The base resource is
productsorusers. category,brand,role, andactiveare filters.
Sorting
GET /products?sort=price_asc
GET /products?sort=price_desc
GET /users?sort=created_atOr with two parameters:
GET /products?sort_by=price&order=ascPagination
GET /products?page=2&limit=20
GET /users?offset=40&limit=20
page / limit and offset / limit are both common patterns.
Combining several options
GET /products?category=phones&brand=apple&sort=price_asc&page=2&limit=10Notice how query parameters are:
- Optional, you can leave them out.
- Not part of the path, they do not identify a specific single resource.
- Perfect for filtering, searching, ordering, and pagination.
Compare:
GET /products/123 # single product identified by path parameter
GET /products?id=123 # filtering list of productsThe first one is usually what you want if you mean “this one product”.
URL Structure and Naming Conventions
URL design also includes small details like separators and casing. For a beginner this might seem minor, but consistent patterns make APIs easier to use.
Lowercase and Hyphens
Common practices:
- Use lowercase letters in URLs.
- Use hyphens (
-) to separate words. - Avoid underscores (
_) and spaces.
Examples:
/products
/product-categories
/shopping-cart
/user-profilesAvoid:
/ProductCategories # UpperCamelCase
/product_categories # underscores
/shopping cart # spaceHyphens are easier to read:
/order-itemsvs/orderitemsvs/order_items
Consistent naming
Pick one word for a concept and use it everywhere.
Bad:
/users
/user-profiles
/clients
/customersBetter:
/users
/users/{userId}/profileOr:
/customers
/customers/{customerId}/ordersWhatever you use, keep it consistent across the whole API.
Resource operations
Sometimes you need a specific operation that is not a simple CRUD action. For example, activating a user account, canceling an order, or publishing a post.
You can represent these as subresources or actions on a resource with separate paths.
Examples:
POST /users/{userId}/activate
POST /orders/{orderId}/cancel
POST /posts/{postId}/publish
POST /carts/{cartId}/checkoutIt is still RESTful because:
- You still use nouns (
activate,cancel) as subresources or conceptual operations on the resource. - You still rely on HTTP methods to describe the kind of request.
Avoid encoding many parameters into the path:
Bad:
POST /orders/{orderId}/cancel/true/forceBetter:
POST /orders/{orderId}/cancel # body or query parameters explain optionsAvoiding Common Anti‑Patterns
There are some URL patterns that look okay at first but usually lead to messy APIs.
Verbs in URLs
As seen before:
Bad:
POST /createUser
GET /getAllUsers
POST /updateUserProfileBetter:
POST /users
GET /users
PATCH /users/{userId}/profileHTTP method in URL
Sometimes you will see things like:
POST /users/create
GET /users/get
POST /users/deleteThe HTTP method (POST, GET, DELETE, etc.) already describes what you want to do, so adding the verb to the URL is redundant.
Better:
POST /users
GET /users
DELETE /users/{userId}Using multiple resource names for the same thing
Bad:
GET /users
GET /accounts
GET /membersIf these mean the same thing, the API becomes confusing. Choose one term.
Encoding filters into the path
Bad:
GET /products/expensive
GET /products/phone/apple
GET /users/active/trueThis mixes resource identity and filters. Use query parameters instead:
GET /products?price=expensive
GET /products?category=phone&brand=apple
GET /users?active=trueVersioning in URLs (Brief Introduction)
API versioning is covered in detail in a later chapter, but URL design interacts with it, so you should see how they fit together.
A common versioning pattern uses the first part of the path:
GET /v1/users
GET /v2/usersOr:
GET /api/v1/users
GET /api/v1/ordersIn that case everything you learned above still applies after the version segment.
For example:
POST /api/v1/users
GET /api/v1/users/{userId}
GET /api/v1/users/{userId}/orders
GET /api/v1/products?category=phones&sort=price_ascRule: If you use versioning in the URL, keep it at the start of the path and keep the rest of the path clean and consistent.
You will later see alternatives such as versioning through headers, but the basic URL design ideas stay the same.
Putting It All Together: Mini Examples
To make it concrete, here are some complete mini API designs to study.
Example 1: Simple Blog API
Resources:
- posts
- comments
- users
URLs:
# Users
GET /users
POST /users
GET /users/{userId}
PATCH /users/{userId}
DELETE /users/{userId}
# Posts
GET /posts
POST /posts
GET /posts/{postId}
PATCH /posts/{postId}
DELETE /posts/{postId}
# Comments for a post
GET /posts/{postId}/comments
POST /posts/{postId}/comments
GET /posts/{postId}/comments/{commentId}
PATCH /posts/{postId}/comments/{commentId}
DELETE /posts/{postId}/comments/{commentId}
# Search and filter
GET /posts?authorId=123
GET /posts?tag=python&page=2&limit=10Example 2: E‑Commerce API
Resources:
- products
- categories
- customers
- orders
- order items
- carts
URLs:
# Products
GET /products
POST /products
GET /products/{productId}
# Categories
GET /categories
GET /categories/{categoryId}
GET /categories/{categoryId}/products # subresource: products in a category
# Customers
GET /customers
POST /customers
GET /customers/{customerId}
GET /customers/{customerId}/orders
# Orders
GET /orders
POST /orders
GET /orders/{orderId}
POST /orders/{orderId}/cancel # special operation
# Carts
GET /carts/{cartId}
POST /carts
POST /carts/{cartId}/items # add item
PATCH /carts/{cartId}/items/{itemId} # change quantity
DELETE /carts/{cartId}/items/{itemId}
POST /carts/{cartId}/checkout # start checkout
# Filters and pagination
GET /products?category=phones&brand=apple&sort=price_asc&page=1&limit=20Notice how:
- Nouns are used for resources.
- Path parameters identify specific resources.
- Query parameters are used for filters and pagination.
- Subresources reflect natural relationships, but nesting is not too deep.
Checklist for Your Own URLs
When you design a new endpoint, run through this quick checklist:
- Is the URL using nouns, not verbs?
Example:/usersinstead of/createUser. - Is the collection plural and consistent?
Example: always/users, not sometimes/user. - Am I using a path parameter for identity?
Example:/users/{userId}for a single user. - Am I using query parameters only for filters, sorting, and pagination?
Example:/users?role=admin&page=2. - Is the nesting at most two levels deep?
Example:/posts/{postId}/comments, not/a/b/c/d/e. - Is the naming clear, lowercase, and using hyphens if needed?
Example:/order-items,/user-profiles.
Following these simple rules will already make your URLs close to what many professional APIs use. Later chapters will build on this design when you implement these URLs with real HTTP methods, request bodies, and response models.
Views: 7
KAHIBARO