REST API Principles — Resources, Methods and Conventions

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
Note: The stateless constraint is the most practically important for MERN development. It means your Express server must never store session state between requests. Each request from React must be self-contained — carrying all authentication information (JWT token) in the request headers. This is why MERN uses JWT authentication instead of server-side sessions: JWTs make each request independently verifiable without any server memory.
Tip: The most common REST design mistake is using verbs in URLs instead of nouns. REST URLs identify resources — the HTTP method is the verb. Write 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.
Warning: REST is a design style, not a strict specification. Many APIs call themselves RESTful while violating core constraints. The most common violations in MERN APIs are: using GET requests to modify data, embedding authentication state on the server (sessions), and using HTTP status 200 for every response including errors. None of these are catastrophic, but they make your API less predictable and harder to consume.

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

🧠 Test Yourself

A developer defines GET /api/deletePost?id=123 to delete a blog post. What REST principles does this violate?