---
title: Blog API
tags: [api, blog, reference]
---

# Blog API

Endpoints live under `/api/blog/*`. The router is [`backend/src/routes/blogRoutes.ts`](../../backend/src/routes/blogRoutes.ts) and delegates to [`blogController`](../../backend/src/services/blog/blogController.ts).

Public endpoints (post listing and reads) are open. Mutating endpoints (create/update/delete) require **admin** role — enforced via `authenticateToken` + `authorizeRoles('admin')`.

Model: [[BlogPost]]. Image uploads use the [[File API]] (`POST /api/files/upload/blog-images`).

## Public reads

### GET /api/blog/posts

**Description:** Paginated list of published blog posts.
**Auth required:** No
**Query params:**
- `page` (default 1)
- `limit` (default 10)
- `category`
- `tag`
- `search` (matches title/excerpt/content)
- `sortBy` (default `publishedAt`)
- `sortOrder` (`asc` | `desc`, default `desc`)
**Response 200:**
```json
{
  "success": true,
  "data": {
    "posts": [BlogPost, ...],
    "pagination": { "page": 1, "limit": 10, "total": 25, "totalPages": 3, "hasMore": true }
  }
}
```

### GET /api/blog/posts/featured

**Description:** Posts with `isFeatured: true`. Used by the home page hero strip.
**Auth required:** No
**Response 200:** `{ success, data: { posts } }`

### GET /api/blog/posts/recent

**Description:** Most recent N published posts. Used by the footer / sidebar.
**Auth required:** No
**Query params:** `limit` (default 5)

### GET /api/blog/posts/search

**Description:** Full-text search across title/excerpt/content/tags.
**Auth required:** No
**Query params:** `q` (required), `page`, `limit`
**Errors:** `400` if `q` is missing or shorter than 2 characters.

### GET /api/blog/posts/:slug

**Description:** Get a single published post by URL slug.
**Auth required:** No
**Response 200:** `{ success, data: { post: BlogPost } }`
**Errors:** `404` not found or not published.
**Side effects:** Increments `viewsCount`.

## Admin (mutating)

All endpoints below require `Bearer JWT` with `role: "admin"`.

### GET /api/blog/admin/posts

**Description:** Admin list including drafts and unpublished posts.
**Auth required:** Bearer JWT (admin)
**Query params:** `page`, `limit`, `status` (`draft` | `published` | `archived`), `search`
**Response 200:** `{ success, data: { posts, pagination } }`

### GET /api/blog/admin/posts/:id

**Description:** Get a post by id (admin view, includes draft fields).
**Auth required:** Bearer JWT (admin)

### POST /api/blog/posts

**Description:** Create a post.
**Auth required:** Bearer JWT (admin)
**Request body:**
```ts
{
  title: string;
  slug?: string;                // auto-generated from title when omitted
  excerpt?: string;
  content: string;              // Markdown / rich text
  coverImage?: string;          // URL from [[File API]]
  category?: string;
  tags?: string[];
  status?: "draft" | "published";  // default "draft"
  isFeatured?: boolean;
  publishedAt?: string;         // ISO date, used when status === "published"
  videoUrl?: string;
  metadata?: Record<string, unknown>;
}
```
**Response 201:** `{ success, data: { post } }`

### PUT /api/blog/posts/:id

**Description:** Update a post. Partial; the body can contain any of the create fields.
**Auth required:** Bearer JWT (admin)

### DELETE /api/blog/posts/:id

**Description:** Hard-delete a post.
**Auth required:** Bearer JWT (admin)
**Response 200:** `{ success: true, message: "Post deleted" }`

## Comments

The codebase does not currently expose a comments API — comments are intentionally disabled. If/when added, they will appear under `/api/blog/posts/:id/comments`.

## Related

- [[BlogPost]]
- [[File API]] (for cover and inline image uploads)
- `backend/BLOG_API_ENDPOINTS.md` (legacy in-repo notes)
- `backend/BLOG_VIDEO_EXAMPLES.md`
