KAHIBARO
Discord Login Register

7.4. RESTful URL Design

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:

A very common mistake is to put verbs in URLs, like this:

text
POST /createUser
POST /addNewUser
GET  /getUserById
POST /banUser

This 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:

text
POST /users          # create a user
GET  /users/{id}     # get a user
DELETE /users/{id}   # delete (or ban) a user

You 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:

text
POST /createPost
GET  /listPosts
POST /publishPost/{id}

Better:

text
POST /posts             # create post
GET  /posts             # list posts
GET  /posts/{id}        # get single post
POST /posts/{id}/publish  # perform a specific operation on a resource

Plural Nouns and Resource Collections

URLs for resources that represent a collection are usually plural nouns.

Common patterns:

ActionMethodURL
List all usersGET/users
Create a new userPOST/users
Get one userGET/users/{userId}
Update one user (full)PUT/users/{userId}
Update one user (partial)PATCH/users/{userId}
Delete one userDELETE/users/{userId}

Why plural?

You will sometimes see singular resources, like:

These are usually singleton resources. For example, every authenticated user has exactly one profile.

Good patterns:

text
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:

text
# 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:

text
/users/{userId}
/orders/{orderId}
/products/{productId}

Here {userId}, {orderId}, and {productId} are placeholders. A real request might look like:

text
GET /users/123
GET /orders/2024-0001
GET /products/iphone-15-pro

The important part is what goes where:

So, to get a specific user, you should use a path parameter.

Good:

text
GET /users/123

Less RESTful:

text
GET /users?id=123

Both 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:

text
GET /users/john.doe
GET /users/jane@example.com
GET /posts/2024/08/hello-world

You 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:

In those cases it can make sense to represent that relationship in the URL path, as a subresource.

Examples:

text
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 post

For orders and items:

text
GET  /orders/{orderId}/items           # list items in an order
POST /orders/{orderId}/items           # add a line item
GET  /orders/{orderId}/items/{itemId}  # get one item

Or for users and addresses:

text
GET  /users/{userId}/addresses
POST /users/{userId}/addresses
GET  /users/{userId}/addresses/{addressId}

Subresources are very common and make sense when:

However, avoid going too deep in nesting.

Bad:

text
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:

text
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:

text
GET /resource?key=value&key2=value2

A few common examples:

Filtering

text
GET /products?category=phones
GET /products?category=phones&brand=apple
GET /users?role=admin&active=true

This says:

Sorting

text
GET /products?sort=price_asc
GET /products?sort=price_desc
GET /users?sort=created_at

Or with two parameters:

text
GET /products?sort_by=price&order=asc

Pagination

text
GET /products?page=2&limit=20
GET /users?offset=40&limit=20

page / limit and offset / limit are both common patterns.

Combining several options

text
GET /products?category=phones&brand=apple&sort=price_asc&page=2&limit=10

Notice how query parameters are:

Compare:

text
GET /products/123               # single product identified by path parameter
GET /products?id=123            # filtering list of products

The 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:

Examples:

text
/products
/product-categories
/shopping-cart
/user-profiles

Avoid:

text
/ProductCategories      # UpperCamelCase
/product_categories     # underscores
/shopping cart          # space

Hyphens are easier to read:

Consistent naming

Pick one word for a concept and use it everywhere.

Bad:

text
/users
/user-profiles
/clients
/customers

Better:

text
/users
/users/{userId}/profile

Or:

text
/customers
/customers/{customerId}/orders

Whatever 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:

text
POST /users/{userId}/activate
POST /orders/{orderId}/cancel
POST /posts/{postId}/publish
POST /carts/{cartId}/checkout

It is still RESTful because:

Avoid encoding many parameters into the path:

Bad:

text
POST /orders/{orderId}/cancel/true/force

Better:

text
POST /orders/{orderId}/cancel        # body or query parameters explain options

Avoiding 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:

text
POST /createUser
GET  /getAllUsers
POST /updateUserProfile

Better:

text
POST  /users
GET   /users
PATCH /users/{userId}/profile

HTTP method in URL

Sometimes you will see things like:

text
POST /users/create
GET  /users/get
POST /users/delete

The HTTP method (POST, GET, DELETE, etc.) already describes what you want to do, so adding the verb to the URL is redundant.

Better:

text
POST   /users
GET    /users
DELETE /users/{userId}

Using multiple resource names for the same thing

Bad:

text
GET /users
GET /accounts
GET /members

If these mean the same thing, the API becomes confusing. Choose one term.

Encoding filters into the path

Bad:

text
GET /products/expensive
GET /products/phone/apple
GET /users/active/true

This mixes resource identity and filters. Use query parameters instead:

text
GET /products?price=expensive
GET /products?category=phone&brand=apple
GET /users?active=true

Versioning 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:

text
GET /v1/users
GET /v2/users

Or:

text
GET /api/v1/users
GET /api/v1/orders

In that case everything you learned above still applies after the version segment.

For example:

text
POST /api/v1/users
GET  /api/v1/users/{userId}
GET  /api/v1/users/{userId}/orders
GET  /api/v1/products?category=phones&sort=price_asc

Rule: 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:

URLs:

text
# 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=10

Example 2: E‑Commerce API

Resources:

URLs:

text
# 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=20

Notice how:

Checklist for Your Own URLs

When you design a new endpoint, run through this quick checklist:

  1. Is the URL using nouns, not verbs?
    Example: /users instead of /createUser.
  2. Is the collection plural and consistent?
    Example: always /users, not sometimes /user.
  3. Am I using a path parameter for identity?
    Example: /users/{userId} for a single user.
  4. Am I using query parameters only for filters, sorting, and pagination?
    Example: /users?role=admin&page=2.
  5. Is the nesting at most two levels deep?
    Example: /posts/{postId}/comments, not /a/b/c/d/e.
  6. 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

Comments

Please login to add a comment.

Don't have an account? Register now!