Course outline · 0% complete

0/29 lessons0%

Course overview →

Resources and Verbs

lesson 4-1 · ~10 min · 11/29

What a dynamic segment does

In the pattern /users/:id from lesson 3-2, :id matches any value in that position and captures it as a param.

A dynamic segment matches any single path piece and records its value, so /users/42 gives params.id equal to "42", which is a string rather than a number.

The string detail is worth carrying forward, because ids get compared and looked up constantly. A database driver may accept the string and a JavaScript comparison against a numeric id will not, so converting early is the habit.

This lesson builds on that, since those ids are how REST APIs point at individual resources. One route pattern serves every user, and the id is what turns a collection endpoint into an item endpoint.

REST in one paragraph

REST is a set of conventions for shaping an API around resources: the nouns of your system (users, orders, posts). Each resource collection gets a URL, and the HTTP method supplies the verb. You never invent action names like /createUser, the method already says it.

IntentMethod + pathSuccess code
list usersGET /users200
read oneGET /users/42200
createPOST /users201
replace / updatePUT /users/42 or PATCH /users/42200
deleteDELETE /users/42204

Five operations, two URL shapes (/users and /users/:id). Every resource in your API repeats this exact grid, which is why developers can guess a well-designed REST API without reading its docs.

Generating conventional endpoints

endpoint(action, resource, id) produces the method and path each action conventionally uses.

function endpoint(action, resource, id) {
  if (action === "list") return "GET /" + resource;
  if (action === "create") return "POST /" + resource;
  if (action === "get") return "GET /" + resource + "/" + id;
  if (action === "update") return "PUT /" + resource + "/" + id;
  if (action === "remove") return "DELETE /" + resource + "/" + id;
  return "unknown";
}

console.log(endpoint("list", "users"));
console.log(endpoint("get", "users", 42));
console.log(endpoint("create", "orders"));
console.log(endpoint("update", "users", 42));
console.log(endpoint("remove", "orders", 7));

Output

GET /users
GET /users/42
POST /orders
PUT /users/42
DELETE /orders/7

The three added lines follow the same pattern as the finished ones, appending "/" + id for the actions that target a single item. The methods are GET, PUT, and DELETE respectively.

The split between the two path shapes is the real content. list and create take no id because they address the collection, and the other three take one because they address a member of it.

Notice that get and list share the GET method and differ only by the id. That is the point of REST rather than an accident, since the method says what kind of operation it is and the path says what it applies to.

The function works for any resource name, so endpoint("get", "orders", 7) needs no new code. That reusability is a direct consequence of the convention, and an API with hand-invented action names could not be generated this way at all.

Note that a real client library is exactly this function with a fetch attached. The reason so many API SDKs look alike is that they are all generated from the same grid.

/v1/users/42/posts?status=draftversioncollectionidsub-collectionquery string: filters, sorting, paging
Anatomy of a REST URL: nouns in the path identify things, the query string refines the question.

URL design rules

The conventions that make an API guessable:

  • Plural nouns: /users, not /user or /userList.
  • No verbs in paths: the method is the verb. POST /users beats /createUser.
  • Nest for ownership: /users/7/posts reads as "the posts of user 7". Keep nesting shallow (one level is usually plenty).
  • Query string for options: filtering, sorting, and paging are refinements of a GET, so they go after ?: /posts?status=draft&sort=newest.
  • Lowercase, hyphens if needed: /blog-posts, never /BlogPosts.

An API that follows these reads like a sentence: GET /users/7/posts?status=draft is "get user 7's draft posts".

Redesigning a badly shaped endpoint

GET /getUserPosts?user=7 becomes GET /users/7/posts.

The user id belongs in the path because it identifies which user's posts are wanted, and get is redundant with the GET method. Query strings are for optional refinements such as ?status=draft, not for identifying the resource itself.

The test for path versus query is whether removing the value leaves a meaningful request. /users/posts means nothing, so the 7 is identity and belongs in the path, and /users/7/posts without ?status=draft still means something, so the status is a refinement.

The nesting reads as ownership, so /users/7/posts is "the posts of user 7". One level of nesting is usually plenty, and /users/7/posts/3/comments/9 is where paths stop being readable and the deeper resource deserves its own top-level route.

ShapeProblem
/getUserPosts?user=7verb in the path, identity in the query
/users/7/postscorrect
/user/7/postssingular noun, breaks the convention
/users/7/getPostsverb again

The payoff for following these rules is guessability. A developer who has used one endpoint of a well-shaped API can predict the rest without opening the documentation, which is worth more than any individual clever URL.