Resource Design: URLs, Nouns, and Hierarchies
Every REST API makes hundreds of design decisions. Most of them can be fixed later. One of them almost can't: how you model your resources and structure your URLs.
Get this wrong and you end up with an API that's confusing to use, hard to extend, and painful to document. Get it right and everything downstream — request design, versioning, client code — becomes cleaner almost automatically.
In the previous article, we built the mental model: REST is an architectural style that uses HTTP's uniform interface to represent operations on resources. Now it's time to make that concrete. What is a resource? How do you turn a product requirement into a URL? When do you nest, and when do you flatten? Why does /tasks beat /getTasks?
That's what this article is about. We'll answer all of it — and by the end, you'll have designed the full URL structure for TaskFlow, the task management API we're building across this module.
Quick Reference
Core rule: URLs identify resources (nouns), HTTP verbs describe what to do with them.
Resource URL patterns:
Collection: /tasks — All tasks
Single item: /tasks/{id} — One specific task
Nested: /projects/{id}/tasks — Tasks belonging to a project
Design checklist:
- ✅ Plural nouns (
/tasks, not/task) - ✅ Lowercase, hyphen-separated (
/task-comments, not/taskComments) - ✅ IDs in the path (
/tasks/99, not/tasks?id=99) - ❌ No verbs in URLs (
/tasks, not/getTasks) - ❌ No deep nesting beyond two levels
Gotchas:
- ⚠️ "Flat with filters" often beats nesting —
/tasks?project=42is sometimes cleaner than/projects/42/tasks - ⚠️ Actions that don't map neatly to CRUD (like publishing or archiving) need a deliberate pattern
- ⚠️ Resource boundaries aren't always obvious from product requirements — they require interpretation
See also:
- REST APIs: What They Are and How They Work — the previous article in this module
- HTTP Methods and Status Codes: The Full Picture (coming soon) — how verbs complete the picture
Version Information
Relevant specifications:
Note: URL design conventions are community standards, not formal specifications. The patterns in this article reflect widely adopted industry practice. Where multiple valid approaches exist, we discuss the tradeoffs.
Last verified: June 2025
What You Need to Know First
Required reading:
- REST APIs: What They Are and How They Work — you need the REST mental model (resources, representations, the uniform interface) before URL design will make sense
Helpful background:
- Familiarity with how URLs work — what a path is, what a query string is
What We'll Cover in This Article
By the end of this guide, you'll understand:
- What a REST resource is and how to identify one in product requirements
- Why URLs should contain nouns, not verbs — and what goes wrong when they don't
- How to design flat and hierarchical URL structures
- When to nest resources and when to flatten them
- How to handle actions that don't map neatly to CRUD operations
- URL naming conventions (pluralization, casing, special characters)
- The complete TaskFlow resource tree
What We'll Explain Along the Way
Don't worry if you're unfamiliar with these — we'll define them as we encounter them:
- CRUD operations (Create, Read, Update, Delete)
- Query parameters vs path parameters
- URL slugs and identifiers
- Idempotency (briefly, with a link to the full treatment in the next article)
What Is a Resource?
Before we can design URLs, we need to know what we're putting in them.
In REST, a resource is any named concept your API exposes — a thing that clients want to interact with. Resources are typically nouns: users, tasks, projects, comments, invoices, orders. A resource can be:
- A single item: one specific task, identified by its ID
- A collection: the set of all tasks, or all tasks belonging to a project
Here's the mental test: Can a client do something useful with this concept independently? Can they create it, retrieve it, update it, or delete it on its own? If yes, it's probably a resource.
Let's apply that test to TaskFlow's product requirements:
Users can create, view, update, and delete tasks. Tasks can be assigned to other users. Tasks belong to projects. Users can comment on tasks.
Let's parse what's a resource and what isn't:
| Concept | Resource? | Why |
|---|---|---|
| User | ✅ Yes | Can be created, retrieved, updated, deleted independently |
| Task | ✅ Yes | Same — the core object of the product |
| Project | ✅ Yes | Groups tasks; can be managed independently |
| Comment | ✅ Yes | Attached to tasks but can be created, retrieved, deleted |
| "Assignment" | 🟡 Maybe | It's a relationship between a task and a user — we'll model it as a field on the task, not its own resource |
| Task status | ❌ No | It's a property of a task, not an independent concept |
Notice that last two. Not everything a product mentions becomes a resource. Assignments are a property (the assignee field on a task). Status is a field. Resources are concepts that stand on their own, not every attribute inside a concept.
Resources vs Actions: Why URLs Are Nouns
Here's the mistake almost every developer makes when designing their first API. It's understandable — when you're writing code, you think in terms of what your system does. And that thinking bleeds into URL design.
Let's look at what a verb-heavy API looks like:
GET /getTasks
GET /getTaskById?id=99
POST /createTask
POST /updateTask?id=99
POST /deleteTask?id=99
POST /markTaskComplete?id=99
This feels natural if you're thinking in terms of function calls. But it creates real problems:
Problem 1: The URL carries redundant information. POST /createTask — you already know from POST that you're creating something. The verb in the URL adds noise without adding information.
Problem 2: The interface stops being uniform. When every API has its own verb vocabulary, clients can't make any assumptions. Does this API use createTask or addTask or newTask? They have to read documentation for every endpoint rather than inferring from a consistent pattern.
Problem 3: Caching breaks. HTTP caches work by URL. If you use POST /getTasks (a verb-named endpoint that reads data), the cache has no idea this is a safe read operation — because POST is supposed to modify state.
The REST solution is elegant: put the noun in the URL, put the verb in the HTTP method. The same /tasks URL expresses all four CRUD operations through the HTTP method:
GET /tasks — Retrieve all tasks
POST /tasks — Create a new task
GET /tasks/99 — Retrieve task 99
PUT /tasks/99 — Replace task 99 entirely
PATCH /tasks/99 — Update part of task 99
DELETE /tasks/99 — Delete task 99
Six operations. One URL pattern. The HTTP method carries all the action semantics. This is the uniform interface working exactly as designed.
The Two URL Patterns: Collection and Item
Every resource in a REST API appears in two URL forms.
The Collection URL
The collection URL refers to the entire set of resources of a given type. It's always a plural noun.
/tasks
/projects
/users
/comments
Operations on the collection:
GET /tasks — List all tasks (typically with filters, pagination)
POST /tasks — Create a new task in the collection
The Item URL
The item URL refers to one specific resource, identified by its ID.
/tasks/99
/projects/42
/users/user_abc
/comments/comment_7
Operations on a single item:
GET /tasks/99 — Retrieve task 99
PUT /tasks/99 — Replace task 99 entirely
PATCH /tasks/99 — Update part of task 99
DELETE /tasks/99 — Delete task 99
These two patterns — collection and item — are the building blocks of every REST URL you'll ever design. Everything else is a combination or extension of these two forms.
Hierarchical Relationships: Nesting Resources
Some resources only make sense in the context of a parent. In TaskFlow, comments only exist on tasks. A comment without a task is meaningless.
This parent-child relationship can be expressed in the URL itself through nesting:
/tasks/99/comments — All comments on task 99
/tasks/99/comments/7 — Comment 7 on task 99
The structure reads naturally: "comments, of task 99." You can see the relationship at a glance.
Let's look at what operations on a nested resource look like:
GET /tasks/99/comments — List all comments on task 99
POST /tasks/99/comments — Add a comment to task 99
GET /tasks/99/comments/7 — Retrieve comment 7
PATCH /tasks/99/comments/7 — Edit comment 7
DELETE /tasks/99/comments/7 — Delete comment 7
The nesting communicates the ownership relationship — comments belong to tasks — and it scopes all operations to the correct context. A POST to /tasks/99/comments will always create a comment on task 99, not on some other task.
Here's the full nesting in a diagram:
Diagram: The TaskFlow URL hierarchy for tasks and comments. The collection URL (/tasks) leads to an item URL (/tasks/{taskId}), which has its own collection (/comments) and item URLs (/tasks/{taskId}/comments/{commentId}). Each level adds one segment.
When to Nest vs When to Flatten
Nesting is powerful, but it has a trap: it can grow too deep.
Here's what deeply nested URLs look like:
/organizations/5/projects/42/tasks/99/comments/7/attachments/3
This is technically correct — that attachment does belong to that comment, on that task, in that project, in that organization. But it's painful to work with. The URL is long, fragile (a wrong parent ID breaks the whole thing), and harder to use in caches and documentation.
The general rule: nest no more than two levels deep.
✅ /projects/42/tasks — One level of nesting
✅ /tasks/99/comments — One level of nesting
⚠️ /projects/42/tasks/99/comments — Two levels — acceptable but borderline
❌ /organizations/5/projects/42/tasks/99/comments — Too deep
The Flattening Alternative
When relationships grow deep, flatten them using query parameters. Instead of:
GET /organizations/5/projects/42/tasks/99/comments
Use:
GET /comments?task=99
Both return the same data. The second is shorter, more flexible (you can filter by multiple tasks at once), and avoids the fragility of deeply nested paths.
Here's how to decide which approach to use:
| Scenario | Approach | Reason |
|---|---|---|
| Resource only makes sense with a parent | Nest | /tasks/99/comments — a comment without a task is meaningless |
| Resource exists independently too | Flatten or dual-route | Tasks can be listed globally with /tasks |
| More than 2 levels deep | Flatten | Deep nesting is unwieldy |
| Client needs to filter cross-parent | Flatten | GET /comments?author=user_42 doesn't fit a nested structure |
Dual Routes: The Practical Middle Ground
For resources that make sense both nested and flat, you can expose both URL patterns:
GET /tasks — All tasks (globally, for admin views)
GET /projects/42/tasks — Tasks in project 42 (scoped view)
Both routes return tasks. The nested version scopes the result to a project. This is common in production APIs and perfectly valid REST. Just make sure both routes return consistent response shapes — same fields, same pagination format.
URL Naming Conventions
REST gives you freedom in URL design. That freedom can produce inconsistency if you don't establish conventions. Here are the standards the industry has settled on.
Pluralize Resource Names
Collections are plural. Always.
✅ /tasks
✅ /users
✅ /projects
❌ /task
❌ /user
Why? A collection is many things. /tasks tells you immediately you're dealing with a collection. /task is ambiguous — is this the task collection, or is "task" just the resource name?
Single items use the same plural base — the ID makes them singular:
GET /tasks/99 ← "The task with ID 99" — no need to change to /task/99
Use Lowercase with Hyphens
URLs are case-insensitive in the host portion, but case-sensitive in the path portion per RFC 3986. To avoid ambiguity and server configuration bugs, always use lowercase.
For multi-word resource names, use hyphens, not underscores or camelCase:
✅ /task-comments
✅ /project-members
❌ /taskComments
❌ /task_comments
❌ /TaskComments
Why hyphens? They're the web convention — search engines and humans both read hyphenated URLs more naturally. Underscores are sometimes hidden by URL underlines in browsers and certain text-rendering contexts.
IDs Belong in the Path, Not the Query String
The ID that identifies a specific resource belongs in the URL path:
✅ GET /tasks/99
❌ GET /tasks?id=99
Path parameters identify which resource. Query parameters filter, sort, or paginate collections. Using a query parameter for an ID blurs this distinction and breaks the collection/item pattern.
Special Characters: Encode or Avoid
URLs have a reserved character set. If your IDs contain special characters, they need to be percent-encoded. Simpler approach: use IDs that don't contain special characters.
Common ID formats in production APIs:
/tasks/99 — Simple integer (fine for small-scale)
/tasks/task_99 — Prefixed integer (type is clear from the ID)
/tasks/t_8kJd92nA — Prefixed random string (no sequential guessing)
/tasks/01J5XKQM4... — ULID (sortable, collision-resistant)
For TaskFlow, we'll use prefixed string IDs like task_8kJd92nA. They're readable, they don't expose row counts, and they're safe in URLs without encoding.
Handling Actions That Don't Fit CRUD
One of the most common sources of confusion in REST API design: what do you do when you need to express an action that doesn't map neatly to Create, Read, Update, or Delete?
In TaskFlow, examples might include:
- Marking a task as complete
- Archiving a project
- Assigning a task to a different user
- Sending a notification
Let's work through the options.
Option 1: Model the Action as a State Change
The cleanest approach is almost always to turn the action into an update on a field. "Mark task complete" is just updating the status field to "complete":
PATCH /tasks/99
Body: { "status": "complete" }
This works for most state transitions. The action is a PATCH on the resource — no new URL pattern needed.
Option 2: Model a Sub-Resource
Sometimes the action creates something meaningful. "Archiving" a project might not just flip a flag — it might trigger a whole process that has its own state. In that case, model it as a sub-resource:
POST /projects/42/archive — Initiate archiving
GET /projects/42/archive — Check archive status
DELETE /projects/42/archive — Restore from archive (un-archive)
This is a legitimate pattern, though it blurs the noun/verb line slightly. archive here is a noun ("the archive of this project"), not a verb.
Option 3: Action Sub-Resource
For truly imperative actions with no natural resource equivalent, some APIs use an explicit action sub-resource:
POST /tasks/99/actions/complete
POST /tasks/99/actions/assign
POST /projects/42/actions/archive
This is a pragmatic escape hatch. Use it sparingly — it's a sign the resource model might need revisiting.
The decision tree for TaskFlow:
| Action | Approach | URL |
|---|---|---|
| Mark task complete | State change | PATCH /tasks/99 + { "status": "complete" } |
| Assign task to user | State change | PATCH /tasks/99 + { "assignee": "user_42" } |
| Archive a project | Sub-resource | POST /projects/42/archive |
| Duplicate a task | Create new resource | POST /tasks + { "copy_of": "task_99" } |
Designing TaskFlow's Resource Tree
Let's put everything together and design the complete URL structure for TaskFlow.
Recall the requirements: users, projects, tasks, and comments. Users can be assigned to tasks. Tasks belong to projects. Comments belong to tasks.
Here's the full resource tree:
Diagram: TaskFlow's resource hierarchy. Users and Projects are top-level collections. Tasks can be accessed globally (/tasks) or scoped to a project (/projects/{id}/tasks). Comments are always nested under the task they belong to.
Now let's enumerate every endpoint:
Users
GET /users — List all users
POST /users — Create a new user account
GET /users/{userId} — Get a specific user's profile
PATCH /users/{userId} — Update a user's profile
DELETE /users/{userId} — Delete a user account
Projects
GET /projects — List all projects
POST /projects — Create a new project
GET /projects/{projectId} — Get a specific project
PATCH /projects/{projectId} — Update a project
DELETE /projects/{projectId} — Delete a project
POST /projects/{projectId}/archive — Archive a project
Tasks
GET /tasks — List all tasks (with filters)
POST /tasks — Create a new task
GET /tasks/{taskId} — Get a specific task
PATCH /tasks/{taskId} — Update a task (status, assignee, etc.)
DELETE /tasks/{taskId} — Delete a task
GET /projects/{projectId}/tasks — List tasks in a project
Note that POST /tasks accepts a project_id in the request body — you don't need POST /projects/{projectId}/tasks to create a task within a project. Both patterns are valid; TaskFlow uses the flat creation route for simplicity.
Comments
GET /tasks/{taskId}/comments — List comments on a task
POST /tasks/{taskId}/comments — Add a comment to a task
GET /tasks/{taskId}/comments/{commentId} — Get a specific comment
PATCH /tasks/{taskId}/comments/{commentId} — Edit a comment
DELETE /tasks/{taskId}/comments/{commentId} — Delete a comment
Comments are always nested under tasks — they have no meaning without a parent task.
The Full Picture
Here's the complete endpoint list in one view:
# Users
GET /users
POST /users
GET /users/{userId}
PATCH /users/{userId}
DELETE /users/{userId}
# Projects
GET /projects
POST /projects
GET /projects/{projectId}
PATCH /projects/{projectId}
DELETE /projects/{projectId}
POST /projects/{projectId}/archive
# Tasks
GET /tasks
POST /tasks
GET /tasks/{taskId}
PATCH /tasks/{taskId}
DELETE /tasks/{taskId}
GET /projects/{projectId}/tasks
# Comments
GET /tasks/{taskId}/comments
POST /tasks/{taskId}/comments
GET /tasks/{taskId}/comments/{commentId}
PATCH /tasks/{taskId}/comments/{commentId}
DELETE /tasks/{taskId}/comments/{commentId}
Twenty endpoints. Four resource types. A structure that's readable on first sight and consistent enough that a developer can correctly predict URLs they haven't seen before.
Common Misconceptions
❌ Misconception: Singular nouns are more "correct" because an ID refers to one thing
Reality: The plural form is the resource type name — not a count. /tasks/99 reads as "the tasks collection, item 99," not "multiple tasks numbered 99." Plural-always is a convention that eliminates ambiguity: you never have to decide whether /task/99 or /tasks/99 is right.
Why this matters: Inconsistency between /task (singular) and /users (plural) in the same API is confusing for API consumers. Pick one convention and apply it everywhere.
❌ Misconception: Nesting always makes the relationship clearer
Reality: Nesting past two levels makes URLs harder to read and harder to use, not clearer. The relationship between resources is better expressed in the response body (e.g., a task response includes a project_id field) than in deeply nested URLs.
Example:
❌ Too deep:
GET /organizations/5/projects/42/tasks/99/comments/7
✅ Cleaner equivalent:
GET /comments/7
Response body: { "task_id": "task_99", "project_id": "proj_42", ... }
❌ Misconception: You need a separate URL pattern for every operation
Reality: The combination of URL + HTTP method handles the vast majority of operations without any new URL patterns. Before inventing a custom URL structure for an action, ask: "Can this be expressed as a state change via PATCH?" It usually can.
Example:
❌ Unnecessary custom action:
POST /tasks/99/markComplete
POST /tasks/99/assignToUser
POST /tasks/99/changeDueDate
✅ All of these are just PATCH:
PATCH /tasks/99
Body: { "status": "complete" }
Body: { "assignee": "user_42" }
Body: { "due_date": "2025-08-01" }
❌ Misconception: Query parameters can substitute for path parameters
Reality: Query parameters are for filtering and options. The identity of a resource — which specific resource you're talking about — belongs in the path. GET /tasks?id=99 is technically possible but semantically wrong. It implies "filter the task collection by ID=99" rather than "retrieve task 99 as a specific resource."
Troubleshooting Common Issues
Problem: "I'm not sure whether this concept should be a resource or a field"
Diagnostic approach: Ask three questions:
- Does a client ever need to interact with this concept independently, without going through a parent?
- Does this concept have its own lifecycle (it can be created, modified, deleted)?
- Would this concept appear in lists or collections on its own?
If yes to two or more: resource. If no: field on a parent resource.
TaskFlow example: "Task status" fails all three tests — you never list statuses independently, statuses don't have their own lifecycle, and you always access status as part of a task. It's a field. "Comment" passes all three — you might list all comments by a user, comments have their own lifecycle, and they appear in lists. It's a resource.
Problem: "I need to support an action that really doesn't fit any HTTP method"
Resolution approach:
- Try modeling it as a state change first (
PATCHwith a body) - If it creates a new thing, try
POSTto a meaningful collection - If it truly has no resource equivalent, use a sub-resource with a noun name:
POST /tasks/99/archive(not/archiveTask) - Reserve
/actions/{name}as a last resort for imperative operations with complex side effects
Problem: "My API needs to support both nested and flat access to the same resource"
Resolution: This is valid and common. Provide both routes, but make sure they return consistent response shapes. Document both clearly. Use the same underlying query logic — the nested route is just the flat route with a pre-applied filter.
// These two routes can share the same handler logic
// GET /tasks?project=proj_42 — explicit filter
// GET /projects/proj_42/tasks — nested (implicitly applies same filter)
async function getTasksHandler(projectId?: string): Promise<Task[]> {
const query = db.tasks.orderBy("created_at", "desc");
if (projectId) {
query.where("project_id", projectId);
}
return query.execute();
}
Problem: "I used verbs in my URLs and now the API is inconsistent — how do I fix it?"
Resolution: You probably can't refactor all at once without breaking existing clients. Options:
- Version the API: Introduce
/v2/with correct noun-based URLs, keep/v1/(with verbs) alive during migration - Add noun aliases: Add the correct URL alongside the existing one, then deprecate the verb form
- Document the inconsistency: At minimum, acknowledge it so future endpoints don't continue the pattern
We'll cover versioning strategy in detail in API Versioning: Strategies and Tradeoffs (coming soon).
Check Your Understanding
Quick Quiz
-
What's wrong with the URL
/getTasksByProject?projectId=42?Show Answer
Two problems:
- The URL contains a verb (
getTasks) — REST URLs should contain nouns only - The operation (reading a filtered list) should be expressed by the HTTP method (
GET) and query parameters, not by the URL itself
Correct design:
GET /projects/42/tasksorGET /tasks?project=42 - The URL contains a verb (
-
When would you choose
GET /tasks?project=42overGET /projects/42/tasks?Show Answer
Use
GET /tasks?project=42when:- You need to filter by multiple projects simultaneously (e.g.,
?project=42&project=43) - The nesting would go deeper than two levels
- Tasks in your system exist independently of projects and you want to emphasize that
- You want a simpler, flatter URL structure
Use
GET /projects/42/taskswhen:- You want to make the parent-child relationship explicit in the URL
- Tasks in your system never exist without a project
- The nested form feels more natural for the API's primary use cases
Both are valid REST. Pick one and be consistent.
- You need to filter by multiple projects simultaneously (e.g.,
-
A user clicks "Mark Complete" in the TaskFlow UI. What API call should the frontend make?
Show Answer
PATCH /tasks/{taskId}
Content-Type: application/json
{ "status": "complete" }"Mark complete" is a state change on an existing resource — it's a
PATCH. There's no need to create a new URL pattern for this action. -
Design the URL structure for a new resource: task attachments (files uploaded to a task).
Show Answer
Attachments are owned by tasks and have no independent meaning, so they should be nested:
GET /tasks/{taskId}/attachments — List attachments on a task
POST /tasks/{taskId}/attachments — Upload a new attachment
GET /tasks/{taskId}/attachments/{attachmentId} — Get a specific attachment
DELETE /tasks/{taskId}/attachments/{attachmentId} — Delete an attachmentNote:
PATCHon an attachment is unusual (you typically don't edit a file in place), so it's omitted. The design follows the same collection/item pattern as comments.
Hands-On Exercise
Challenge: The TaskFlow team has added two new requirements:
- Tasks can have multiple "labels" (like tags: "urgent", "bug", "feature"). Labels exist globally and can be applied to many tasks.
- A team lead can "pin" certain tasks to the top of a project view. Pinning is scoped to a project.
Design the URL structure for both features. Consider: are these resources, sub-resources, or fields? When should you nest?
Show Answer
Labels:
Labels exist globally (you can list all labels and reuse them across tasks), so they deserve their own top-level resource:
GET /labels — List all available labels
POST /labels — Create a new label
GET /labels/{labelId} — Get a label
PATCH /labels/{labelId} — Edit a label (rename, change color)
DELETE /labels/{labelId} — Delete a label
Applying labels to tasks is a relationship, not a resource — model it as a field on the task:
PATCH /tasks/{taskId}
Body: { "label_ids": ["label_1", "label_2"] }
Or, if the relationship needs its own operations (add/remove one label at a time):
PUT /tasks/{taskId}/labels/{labelId} — Apply a label to a task
DELETE /tasks/{taskId}/labels/{labelId} — Remove a label from a task
Pinned Tasks:
Pinning is scoped to a project view — a task is "pinned in project 42," not pinned globally. This is a relationship between a project and a task. Model it as a sub-resource of the project:
GET /projects/{projectId}/pinned-tasks — List pinned tasks in a project
PUT /projects/{projectId}/pinned-tasks/{taskId} — Pin a task to a project
DELETE /projects/{projectId}/pinned-tasks/{taskId} — Unpin a task
Using PUT for pinning is intentional: pinning the same task twice should be idempotent (same result whether you do it once or three times).
Summary: Key Takeaways
-
Resources are the nouns of your API — things clients interact with (users, tasks, projects, comments), not actions. If a concept can be created, retrieved, updated, or deleted independently, it's probably a resource.
-
URLs identify resources; HTTP methods describe what to do with them. The same
/tasksURL handles create, list, retrieve, update, and delete through the HTTP method — you never need to put verbs in URLs. -
Two patterns cover almost everything: collection URLs (
/tasks) and item URLs (/tasks/99). Nested variants (/tasks/99/comments) express parent-child ownership. -
Nest at most two levels deep. Deep nesting creates fragile, unreadable URLs. Flatten with query parameters (
/comments?task=99) when relationships grow complex. -
Naming conventions matter: plural nouns, lowercase, hyphens between words, IDs in the path (not the query string).
-
Actions that don't fit CRUD are usually state changes in disguise. "Mark complete," "archive," and "assign" all map to
PATCHoperations on existing resources. Invent new URL patterns only as a last resort. -
TaskFlow's full resource tree — users, projects, tasks, and comments — can be expressed in 20 endpoints using these four patterns consistently.
What's Next?
You now know how to model resources and structure URLs. The noun side of the REST equation is covered.
The natural next step is HTTP Methods and Status Codes: The Full Picture (coming soon) — where we complete the verb side. You've seen GET, POST, PATCH, and DELETE in examples, but there are nine HTTP methods in total, and each one has precise semantics around safety and idempotency that affect how you design every endpoint in TaskFlow.
Once you understand methods and status codes in depth, you'll have everything you need to move on to Request & Response Design: Payloads, Headers, and Conventions (coming soon) — where we shape what actually travels over the wire.
References
- RFC 3986 — Uniform Resource Identifier (URI): Generic Syntax — IETF specification defining URI structure, path segments, and query components. Foundation for the "IDs in path vs query string" and naming conventions discussion.
- RFC 7231 — HTTP/1.1 Semantics and Content — Formal definitions of HTTP method semantics referenced throughout.
- Architectural Styles and the Design of Network-based Software Architectures — Roy Fielding's dissertation. The uniform interface constraint (Section 5.1.5) is the foundation for the nouns-not-verbs principle.
- REST API Tutorial — Resource Naming — A widely referenced community guide to REST resource naming conventions, used as a cross-reference for plural nouns and hyphenation standards.