Designing API Endpoints for the MERN Blog

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
Note: The endpoint map below uses two conventions you should adopt for every MERN project. First, all endpoints are prefixed with /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.
Tip: For the Blog API, comments are a nested resource under posts (/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.
Warning: Avoid deeply nested URLs beyond two levels. /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

🧠 Test Yourself

You need an endpoint that lets users "like" a post. Which REST design is most appropriate?