Welcome to our comprehensive guide on RESTful Routes! REST (REpresentational State Transfer) may sound fancy, but at its core, it’s about a straightforward approach to structuring web application endpoints that everyone can understand.
Route: The URL path for a request, for example /tweets.
Endpoint: A combination of the HTTP method (verb) + route that defines how the server
should process the request and what the response should look like. For instance,
GET /tweets vs. POST /tweets are two separate endpoints, each describing
a different “action” on the /tweets path.
Think of a route like an address in a city (e.g., Main St.), and the endpoint as not just the address but also the instruction you give the delivery driver (“Pick up a package” or “Drop off a package”). Both the address (route) and the instruction (HTTP verb) determine the final action.
GET /users | Route: /usersPOST /users | Route: /usersPOST /session | Route: /sessionREST prescribes a uniform and predictable way to define endpoints, making it easier for fellow developers to integrate with or understand your web service.
Sometimes a URL needs to capture dynamic values—like user IDs or tweet IDs. A route parameter is a placeholder in the path. For example:
/tweets/:tweetId
This “:tweetId” can represent any valid ID, such as
/tweets/17 or /tweets/123abc. Route parameters allow you to have
flexible paths while maintaining a single endpoint definition in your code or documentation.
REST (REpresentational State Transfer) defines an architecture style, not an official standard. However, there are generally accepted constraints or “rules”:
In practice, you can think of CRUD operations
(Create, Read, Update, Delete) mapped to the standard HTTP verbs
(POST, GET, PUT/PATCH, DELETE).
RESTful routes often align with the data entities in your application. If you’re building a Twitter clone, you’ll have “tweets,” “users,” “comments,” etc. The route structure typically follows two patterns:
A collection URL (e.g., /tweets) represents all tweets.
A singular URL (e.g., /tweets/17) represents one specific tweet with the ID “17”.
/invoices -- All invoices
/invoices/PK-200201 -- A single invoice with id PK-200201
/people -- All people
/people/10103 -- Person with id 10103
/houses -- All houses
/houses/bdfa5ef9-0c86-... -- A specific house by GUID
For our Twitter example, /my/tweets gets the entire collection of your tweets,
while /my/tweets/17 points to one tweet.
Sometimes, the ID section of a route might be replaced with words like
current or latest to denote a specific “record” that changes over time
(like /weather/current or /weather/latest). This is a design choice
that’s still RESTful—just with a custom identifier instead of a numeric or GUID-based ID.
Traditional HTML forms can only send GET and POST requests,
so a common practice is to “simulate” PUT and DELETE through
special routes or hidden form fields. Still, we can keep endpoints “RESTful” in concept.
| Path Pattern | HTTP Verb | Meaning |
|---|---|---|
| /resource-name | GET | Index page: Get an HTML list of the resource |
| /resource-name/new | GET | Create form page: Show a form to create a new record |
| /resource-name | POST | Submit create form: Create a new record for the resource |
| /resource-name/:record-id | GET | Detail page: Show details of a single record |
| /resource-name/:record-id/edit | GET | Edit form page: Show form to edit the specified record |
| /resource-name/:record-id | POST | Submit edit form: Update the specified record |
| /resource-name/:record-id/delete | POST | Submit delete form: Delete the specified record |
For a Twitter clone’s “tweet” resource, you might have:
| Path | HTTP Verb | Meaning |
|---|---|---|
| /my/tweets | GET | Index page: List all your tweets |
| /my/tweets/new | GET | Create form page: Show a form to create a new tweet |
| /my/tweets | POST | Submit create form: Create a new tweet |
| /my/tweets/17 | GET | Detail page: Show the details of tweet #17 |
| /my/tweets/17/edit | GET | Edit form page: Show a form to edit tweet #17 |
| /my/tweets/17 | POST | Submit edit form: Update tweet #17 |
| /my/tweets/17/delete | POST | Submit delete form: Delete tweet #17 |
Notice how each “GET” route corresponds to a page that shows or lists content, and each “POST” route either creates, updates, or deletes something. After these actions, you typically redirect to another page, like the tweet’s detail page or your tweets list.
Sometimes a resource “lives under” another resource. For example, “comments” might depend on a specific “tweet.” This leads to nested routes:
POST /tweets/:tweetId/comments -- Create a comment for a specific tweet
GET /tweets/:tweetId/comments -- List all comments for a specific tweet
Here, tweetId is a path parameter telling the server which tweet
you’re referring to. This makes it clear that these comments belong to that particular tweet,
and not any others.
| Path Pattern | HTTP Verb | Meaning |
|---|---|---|
| /resource-name/:record-id/nested-resource | GET | Index page of nested resource for a specific record |
| /resource-name/:record-id/nested-resource/new | GET | Form page: create a new nested resource record |
| /resource-name/:record-id/nested-resource | POST | Submit create form: create a new nested resource record |
| /nested-resource/:nested-record-id | GET | Detail page: Show the details of the nested resource record |
| /nested-resource/:nested-record-id/edit | GET | Form page: edit an existing nested resource record |
| /nested-resource/:nested-record-id | POST | Submit edit form: update the nested resource record |
| /nested-resource/:nested-record-id/delete | POST | Submit delete form: remove the nested resource record |
REST isn’t the only game in town! There are many ways to structure web application
endpoints. Some APIs might use /api/v1/doSomething,
while others might use GraphQL or custom RPC-style endpoints.
However, RESTful routes remain popular because they are easy to read
and consistent across a wide variety of applications.
Suppose you want to build a simple Express.js application that follows RESTful routing for a “tweets” resource. Here’s a minimal example:
const express = require('express');
const app = express();
// Middleware to parse incoming request body
app.use(express.json());
// 1) Index page: get a list of tweets
app.get('/tweets', (req, res) => {
// Imagine you fetch tweets from a database
res.json({ message: 'Here are all the tweets!' });
});
// 2) Create form page (in a real app, you'd serve an HTML form here)
app.get('/tweets/new', (req, res) => {
res.send('<form>...create tweet form...</form>');
});
// 3) Submit create form: add new tweet
app.post('/tweets', (req, res) => {
// Insert tweet into a database
res.redirect('/tweets');
});
// 4) Detail page: see one tweet
app.get('/tweets/:tweetId', (req, res) => {
const tweetId = req.params.tweetId;
res.send(`Detail view of tweet #${tweetId}`);
});
// 5) Edit form page
app.get('/tweets/:tweetId/edit', (req, res) => {
const tweetId = req.params.tweetId;
res.send(`<form>Edit tweet #${tweetId}</form>`);
});
// 6) Submit edit form
app.post('/tweets/:tweetId', (req, res) => {
const tweetId = req.params.tweetId;
// Update tweet in database
res.redirect('/tweets/' + tweetId);
});
// 7) Submit delete form
app.post('/tweets/:tweetId/delete', (req, res) => {
const tweetId = req.params.tweetId;
// Delete the tweet from the database
res.redirect('/tweets');
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
Notice how each app.VERB corresponds to a different endpoint.
Each route is structured in a clear, predictable manner.
/tweets) vs. Single Resources
(e.g., /tweets/:tweetId) let you create intuitive CRUD paths.
GET and POST.
/tweets/:tweetId/comments).
By understanding and applying RESTful design principles, you’ll create APIs that are easier to maintain, document, and consume. In your next projects, try experimenting with different naming conventions and see how naturally others can navigate your endpoints!