2 min read

Don’t Be Weird with URLs: RESTful Routing That Makes Sense

If your endpoint looks like a Mad Libs generator had a panic attack, it’s time for a reset. RESTful routing is about clarity, consistency, and just enough structure.

https://giphy.com/gifs/math-lady-meme-WRQBXSCnEFJIuxktnw
https://giphy.com/gifs/math-lady-meme-WRQBXSCnEFJIuxktnw

Let’s get real: API URLs should not look like encrypted passwords. Or like this:

/getAllUserDataNowPlease  
/users/getUserById/123  
/api/alphaVersionTwoPointZero/users/details?id=123

If your endpoint looks like a Mad Libs generator had a panic attack, it’s time for a reset. RESTful routing is about clarity, consistency, and just enough structure.


Rule #1: Keep URLs Clean, Plural, and Resource-Centric

You’re not writing poetry. You’re exposing resources.

Good:

/users  
/users/42  
/posts/109/comments

Bad:

/getAllUsers  
/user?id=42  
/commentListByPostId/109

Keep it simple:

  • Use lowercase
  • Use plural nouns
  • Use forward slashes to indicate hierarchy

Rule #2: Use Nested Routes Only When They Belong

Nested routes make sense only when one resource clearly belongs to another.

If you can’t say it out loud without sounding weird, it’s probably wrong.

Use nesting:

GET /users/42/posts     ← Posts *belong* to a user  
GET /orders/9/items     ← Items *belong* to an order
GET /products/123/categories/9     ← Why? Category isn’t owned by product.

If the resources are independent, keep them separate.


Example: Nested Resources in Code (TypeScript + Express)

// Get all posts by a user  
app.get('/users/:userId/posts', (req, res) => {  
  const { userId } = req.params;  
  const posts = findPostsByUser(userId);  
  res.json(posts);  
});  
  
// Get a specific post by that user  
app.get('/users/:userId/posts/:postId', (req, res) => {  
  const { userId, postId } = req.params;  
  const post = findPostByUser(userId, postId);  
  if (!post) return res.status(404).json({ error: 'Post not found' });  
  res.json(post);  
});

Bonus: Handling Flat Routes for Independent Resources

// Get all categories  
app.get('/categories', (req, res) => {  
  const categories = getAllCategories();  
  res.json(categories);  
});

See how clean that is? No nesting needed — because a category isn't owned by a product.


Rule #3: Versioning Your API — Path vs Header

You’ll need API versioning eventually. Don’t overthink it.

Option A: Path versioning (simple, common)

GET /v1/users

This is clean and self-explanatory. Works well for public APIs.

Option B: Header versioning (cleaner, but more advanced)

GET /users  
Accept: application/vnd.myapp.v2+json

Great for internal APIs or where you want to support many versions invisibly.

Never do this:

GET /users?version=2.0.1

Query params are for filtering, not for structuring your whole API lifecycle.


Recap

  • Keep routes clean, lowercase, and plural
  • Use nesting only for true parent-child relationships
  • Version your API with paths or headers, not query strings
  • Think like a user — if the URL doesn’t make sense when you read it out loud, rethink it

Coming Up Next:

Part 3: “HTTP Status Codes Aren’t Optional: Stop Using 200 for Everything”
We’ll cover the top status codes you should be using, and how to craft consistent, helpful error messages.