REST (Representational State Transfer) is an architectural style for designing networked APIs. It is not a protocol or a standard — it is a set of six constraints that, when followed, produce APIs that are intuitive, scalable, and easy to consume. In the MERN stack, your Express server is the REST API layer. Understanding REST principles gives you the vocabulary and the rules to design an API that your React frontend can call in a predictable way, that other developers can understand without reading documentation, and that can grow without breaking existing clients.
The Six REST Constraints
| Constraint | What It Means | How MERN Satisfies It |
|---|---|---|
| Client-Server | UI and data storage are separated | React (client) is completely separate from Express+MongoDB (server) |
| Stateless | Each request contains all information needed — no server-side session | JWT tokens carry auth state in every request header |
| Cacheable | Responses should declare whether they can be cached | GET responses include Cache-Control headers via Express |
| Uniform Interface | Consistent resource identification and manipulation | Resources identified by URLs, manipulated via HTTP methods |
| Layered System | Client cannot tell if it is connected directly to the server or a proxy | React calls the same API whether behind Nginx, a CDN, or directly |
| Code on Demand (optional) | Server can send executable code to client | Not commonly used in JSON REST APIs |
POST /api/posts (create a post), not POST /api/createPost. Write DELETE /api/posts/123 (delete a post), not POST /api/deletePost/123. The HTTP method already tells you the action — put the resource in the URL.HTTP Methods Mapped to CRUD
| HTTP Method | CRUD | Idempotent? | Has Body? | Use for |
|---|---|---|---|---|
| GET | Read | Yes | No | Retrieve one or many resources |
| POST | Create | No | Yes | Create a new resource |
| PUT | Update (replace) | Yes | Yes | Replace an entire resource with new data |
| PATCH | Update (partial) | No (usually) | Yes | Update only the supplied fields of a resource |
| DELETE | Delete | Yes | Rarely | Remove a resource |
RESTful URL Design Rules
Rule 1 — Use nouns, not verbs
❌ GET /api/getPosts
❌ POST /api/createPost
✅ GET /api/posts
✅ POST /api/posts
Rule 2 — Use plural nouns for collections
❌ GET /api/post (singular — implies one, but returns many)
✅ GET /api/posts (plural — clearly a collection)
Rule 3 — Use IDs for specific resources
✅ GET /api/posts/64a1f2b3 (get one post)
✅ PUT /api/posts/64a1f2b3 (replace one post)
✅ DELETE /api/posts/64a1f2b3 (delete one post)
Rule 4 — Nest for sub-resources
✅ GET /api/posts/64a1f2b3/comments (comments belonging to a post)
✅ POST /api/posts/64a1f2b3/comments (add a comment to a post)
Rule 5 — Use query strings for filtering, sorting, pagination
✅ GET /api/posts?tag=mern&sort=createdAt&order=desc&page=2&limit=10
Rule 6 — Lowercase, hyphens for multi-word resources
❌ /api/blogPosts (camelCase)
❌ /api/BlogPosts (PascalCase)
✅ /api/blog-posts (kebab-case) or /api/posts (preferred — keep it simple)
REST Response Conventions
| Operation | Method | Success Code | Response Body |
|---|---|---|---|
| List all | GET /posts | 200 | { success, count, data: [...] } |
| Get one | GET /posts/:id | 200 | { success, data: {...} } |
| Create | POST /posts | 201 | { success, data: newPost } |
| Update | PUT/PATCH /posts/:id | 200 | { success, data: updatedPost } |
| Delete | DELETE /posts/:id | 200 or 204 | { success, message } or empty |
REST vs Non-REST — Comparison
Non-REST API (verb-based, inconsistent):
POST /api/getPost?id=123 ← wrong method for read
POST /api/updatePost ← verb in URL, method mismatch
GET /api/deletePost?id=123 ← GET should never modify data
POST /api/doLogin ← verb in URL
POST /api/addComment/123 ← verb mixed with ID
REST API (noun-based, consistent):
GET /api/posts/123 ← read
PATCH /api/posts/123 ← partial update
DELETE /api/posts/123 ← delete
POST /api/auth/login ← create session / auth token
POST /api/posts/123/comments ← create nested resource
Common Mistakes
Mistake 1 — Using POST for everything
❌ Wrong — treating your API like RPC with POST for all operations:
POST /api/posts/get ← should be GET /api/posts
POST /api/posts/delete ← should be DELETE /api/posts/:id
POST /api/posts/update ← should be PUT or PATCH /api/posts/:id
✅ Correct — use the HTTP method that describes the operation:
GET /api/posts ← read collection
DELETE /api/posts/:id ← delete resource
PATCH /api/posts/:id ← partial update
Mistake 2 — Inconsistent response shapes
❌ Wrong — different endpoints return different structures:
GET /api/posts → [{ id, title }] (plain array)
GET /api/posts/1 → { post: { id, title } } (nested under 'post')
POST /api/posts → { newPost: { id, title } } (nested under 'newPost')
✅ Correct — consistent envelope across all endpoints:
GET /api/posts → { success: true, count: 3, data: [...] }
GET /api/posts/1 → { success: true, data: { id, title } }
POST /api/posts → { success: true, data: { id, title } }
Mistake 3 — Embedding the API version in every URL instead of using a base prefix
❌ Wrong — version in every individual route:
app.get('/v1/posts', handler)
app.get('/v1/users', handler)
app.get('/v1/auth/login', handler) ← repeated everywhere
✅ Correct — mount all routes under a versioned prefix once:
app.use('/api/v1/posts', postsRouter);
app.use('/api/v1/users', usersRouter);
app.use('/api/v1/auth', authRouter);
Quick Reference
| Pattern | URL + Method |
|---|---|
| List collection | GET /api/posts |
| Get single resource | GET /api/posts/:id |
| Create resource | POST /api/posts |
| Replace resource | PUT /api/posts/:id |
| Partial update | PATCH /api/posts/:id |
| Delete resource | DELETE /api/posts/:id |
| Nested collection | GET /api/posts/:id/comments |
| Create nested resource | POST /api/posts/:id/comments |
| Filter + paginate | GET /api/posts?tag=mern&page=2&limit=10 |