--- type: api title: Blog Endpoints description: Public blog listing and administrator-only post management endpoints. tags: [api, blog, admin, markdown] timestamp: 2026-07-23T10:12:24Z --- # Endpoints | Method | Path | Access | Behavior | |---|---|---|---| | `GET` | `/api/v1/blog/posts` | Public | Returns published posts only, newest publication first. | | `GET` | `/api/v1/admin/blog/posts` | Admin | Returns drafts and published posts, ordered by most recently updated. | | `GET` | `/api/v1/admin/blog/posts/:id` | Admin | Returns one post or `404 NOT_FOUND`. | | `POST` | `/api/v1/admin/blog/posts` | Admin + CSRF | Creates a draft or published post; defaults to draft. | | `PUT` | `/api/v1/admin/blog/posts/:id` | Admin + CSRF | Partially updates title, Markdown body, or status. | | `DELETE` | `/api/v1/admin/blog/posts/:id` | Admin + CSRF | Deletes a post and returns `204`; missing posts return `404 NOT_FOUND`. | The public route is marked `@Public()`. Admin routes require a Bearer JWT with the administrator role, and unsafe requests use the standard CSRF cookie/header contract from [REST API Overview](/api/rest-overview.md). # Schema ## Public list response ```json { "posts": [ { "id": "post-id", "title": "Event update", "publishedAt": "2026-07-23T10:00:00.000Z", "bodyMd": "# Welcome" } ] } ``` Drafts and administrative fields such as `status`, `createdAt`, and `updatedAt` are excluded. ## Admin post | Field | Type | Description | |---|---|---| | `id` | string | Server-generated UUID. | | `title` | string | Trimmed title, required, maximum 200 characters. | | `bodyMd` | string | Markdown body, maximum 200,000 characters. | | `status` | `draft` or `published` | Visibility state. | | `createdAt` | ISO 8601 string | Creation time. | | `updatedAt` | ISO 8601 string | Last persistence update. | | `publishedAt` | ISO 8601 string or null | First publication time. | `POST` accepts `title`, optional `bodyMd`, and optional `status`. `PUT` accepts any subset of those fields. Validation failures use HTTP 400 with code `VALIDATION_FAILED`. # Publication behavior * Creating with `status: "published"` sets `publishedAt`; omitted status creates a draft. * Publishing a never-published draft sets `publishedAt` once. * Editing a published post does not change `publishedAt`. * Moving a post back to draft preserves `publishedAt`; republishing retains the original timestamp. * Only rows currently marked `published` are returned by the public endpoint. # Wiring `BlogModule` registers `BlogController`/`BlogService` for the public list and `AdminBlogController`/`AdminBlogService` for management. Both services access `BlogPostEntity` through `TypeOrmModule.forFeature([BlogPostEntity])`. The Angular `BlogApiService` calls these routes for the admin page, authenticated Blog page, and landing-page post list. # See also - [Blog Publishing and Reading](/guides/blog.md) - [Blog Post Table](/database/blog-posts.md) - [REST API Overview](/api/rest-overview.md)