Before writing a single line of implementation code, a well-designed REST API starts on paper — or in a table. Designing your endpoint map upfront forces you to think through resource relationships, HTTP method choices, authentication requirements, and response shapes before you commit them in code. Changes to your API design after React is already consuming it are painful. In this lesson you will design the complete endpoint map for the MERN Blog application — the same API you will implement throughout the rest of this series.
MERN Blog — Resource Overview
| Resource | Description | Base Path |
|---|---|---|
| Auth | Registration, login, token refresh, password reset | /api/auth |
| Users | User profiles, avatar upload, account management | /api/users |
| Posts | Blog posts with rich content, tags, publish status | /api/posts |
| Comments | Comments on posts, nested under posts | /api/posts/:id/comments |
| Tags | Tag list for filtering posts | /api/tags |
| Upload | Image and file upload for post cover images | /api/upload |
/api to clearly separate API routes from any static file routes. Second, protected routes are marked — your Express middleware layer uses this map to know which routes need JWT verification and which do not. Never add auth to a route mid-build — design it in from day one./api/posts/:postId/comments). This correctly communicates that comments belong to a post and cannot exist independently. An alternative is a flat /api/comments?postId=:id design — both are valid REST, but nested is more semantically clear for resources that are tightly coupled to their parent./api/posts/:postId/comments/:commentId/likes is awkward to work with on both the server and client. If a sub-resource makes sense independently, give it a top-level endpoint and use query parameters for filtering. For example, GET /api/likes?commentId=:id is cleaner than GET /api/posts/:id/comments/:id/likes.Auth Endpoints
| Method | Endpoint | Description | Auth | Status |
|---|---|---|---|---|
| POST | /api/auth/register | Create new user account | Public | 201 |
| POST | /api/auth/login | Authenticate and receive JWT | Public | 200 |
| GET | /api/auth/me | Get currently authenticated user | Protected | 200 |
| POST | /api/auth/logout | Invalidate refresh token | Protected | 200 |
| POST | /api/auth/forgot-password | Send password reset email | Public | 200 |
| PATCH | /api/auth/reset-password/:token | Reset password with token from email | Public | 200 |
Posts Endpoints
| Method | Endpoint | Description | Auth | Status |
|---|---|---|---|---|
| GET | /api/posts | List published posts (paginated, filterable) | Public | 200 |
| GET | /api/posts/featured | List featured posts | Public | 200 |
| GET | /api/posts/:id | Get single post by ID | Public | 200 |
| GET | /api/posts/slug/:slug | Get post by URL slug | Public | 200 |
| POST | /api/posts | Create a new post (draft or published) | Protected | 201 |
| PUT | /api/posts/:id | Replace post entirely | Protected (owner) | 200 |
| PATCH | /api/posts/:id | Update post fields | Protected (owner) | 200 |
| DELETE | /api/posts/:id | Delete post | Protected (owner/admin) | 200 |
| PATCH | /api/posts/:id/publish | Publish a draft post | Protected (owner) | 200 |
Comments Endpoints (Nested Under Posts)
| Method | Endpoint | Description | Auth | Status |
|---|---|---|---|---|
| GET | /api/posts/:postId/comments | Get all comments for a post | Public | 200 |
| POST | /api/posts/:postId/comments | Add a comment to a post | Protected | 201 |
| PATCH | /api/posts/:postId/comments/:id | Edit your comment | Protected (owner) | 200 |
| DELETE | /api/posts/:postId/comments/:id | Delete a comment | Protected (owner/admin) | 200 |
Users Endpoints
| Method | Endpoint | Description | Auth | Status |
|---|---|---|---|---|
| GET | /api/users/:id | Get public user profile | Public | 200 |
| GET | /api/users/:id/posts | Get all posts by a user | Public | 200 |
| PATCH | /api/users/:id | Update user profile | Protected (self/admin) | 200 |
| DELETE | /api/users/:id | Delete user account | Protected (self/admin) | 200 |
| POST | /api/users/:id/avatar | Upload user avatar image | Protected (self) | 200 |
| GET | /api/users | List all users (admin only) | Protected (admin) | 200 |
Tags and Upload Endpoints
| Method | Endpoint | Description | Auth | Status |
|---|---|---|---|---|
| GET | /api/tags | List all tags with post counts | Public | 200 |
| GET | /api/tags/:slug | Get posts for a specific tag | Public | 200 |
| POST | /api/upload/image | Upload post cover image | Protected | 200 |
Query String Conventions
Standard query parameters supported across collection endpoints:
page integer Page number (default: 1)
limit integer Items per page (default: 10, max: 100)
sort string Field to sort by (default: createdAt)
order string Sort direction: asc | desc (default: desc)
search string Full-text search query
tag string Filter by tag slug
author string Filter by author ID
published boolean Filter by publish status (admin only)
from date Filter createdAt >= date (ISO 8601)
to date Filter createdAt <= date (ISO 8601)
Examples:
GET /api/posts?page=2&limit=5&sort=title&order=asc
GET /api/posts?tag=mern&search=hooks&published=true
GET /api/posts?author=64a1f2b3&from=2025-01-01&to=2025-12-31
Common Mistakes
Mistake 1 — Designing endpoints as you code instead of upfront
❌ Wrong — adding endpoints one by one as features are requested without a plan:
Week 1: POST /api/posts
Week 2: GET /api/getPost/:id ← verb in URL — would have caught this upfront
Week 3: POST /api/posts/delete ← wrong method — same problem
Week 4: GET /api/user-posts?id= ← inconsistent resource naming
✅ Correct — design the full endpoint map before writing any implementation. Every URL and method choice is visible and reviewable before code is written.
Mistake 2 — Not versioning the API
❌ Wrong — no versioning means breaking changes affect all existing clients immediately:
GET /api/posts ← if you change the response shape, all React code breaks
✅ Correct — add a version prefix from day one even if you only have v1:
GET /api/v1/posts ← when you introduce breaking changes, deploy v2 alongside v1
Mistake 3 — Mixing action verbs with REST resource nouns
❌ Wrong — hybrid design that is half-REST, half-RPC:
POST /api/posts/publish/:id ← 'publish' is a verb
POST /api/posts/like/:id ← 'like' is a verb
✅ Correct — model state changes as resource updates or sub-resources:
PATCH /api/posts/:id/publish ← or PATCH /api/posts/:id with {published: true}
POST /api/posts/:id/likes ← 'likes' is a noun (collection of likes)
Quick Reference
| Resource | List | One | Create | Update | Delete |
|---|---|---|---|---|---|
| posts | GET /posts | GET /posts/:id | POST /posts | PATCH /posts/:id | DELETE /posts/:id |
| users | GET /users | GET /users/:id | POST /auth/register | PATCH /users/:id | DELETE /users/:id |
| comments | GET /posts/:id/comments | — | POST /posts/:id/comments | PATCH /posts/:id/comments/:id | DELETE /posts/:id/comments/:id |
| tags | GET /tags | GET /tags/:slug | — | — | — |