In short
A CMS is where writers create, edit and publish posts. Building one makes you use almost everything in this phase: forms, uploads, auth, lists and error handling, all in one place.
A content management interface for creating, editing and publishing blog posts — practice for rich forms, file uploads (cover images) and CRUD operations.

Think of it like this
The college magazine office
Students submit articles, the editor fixes them, adds a cover photo, and decides what goes in this month's issue. Your Blog CMS is that office as an app: drafts, edits, images, and a publish button.
Watch it happen
Publishing a post with a cover
POST /uploads (cover)
1/6The author picks a cover image. It uploads first as multipart form data.
The big ideas
What you need to know
Full CRUD
Create, read, update and delete posts. Each action maps to POST, GET, PATCH and DELETE.
Cover image upload
Use FormData to upload a cover, then save the returned image URL with the post.
Drafts vs published
A status field lets you filter the list and only show published posts to readers.
Login required
Only logged-in authors can reach the editor. Handle 401 by sending them to login.
How it works
Build plan
- 1
Post list
Fetch posts with pagination, plus a filter for draft or published.
- 2
Editor form
Title, body and status fields, with validation and a disabled Save while submitting.
- 3
Cover upload
Pick, preview and upload an image, then store its URL in the post.
- 4
Edit and delete
Reuse the same form for editing. Confirm before delete.
- 5
Polish
Add loading skeletons, error messages and a login gate.
See it in code
Walk through the code
1type PostInput = { title: string; body: string; status: "draft" | "published" };2 3export async function savePost(input: PostInput, id?: number) {4 const res = await fetch(id ? `/api/posts/${id}` : "/api/posts", {5 method: id ? "PATCH" : "POST",6 headers: { "Content-Type": "application/json" },7 body: JSON.stringify(input),8 });9 if (!res.ok) throw new Error(`Save failed (${res.status})`);10 return res.json();11}1/4The shape of a post the form sends, including its status.
One function for both create and edit. No id means a new post (POST), an id means update that post (PATCH).
Save yourself the pain
Common mistakes
Oops
Building separate create and edit forms that slowly drift apart.
Do this instead
Use one form component. Pass existing values in for editing and leave it empty for new posts.
Oops
Deleting a post on a single click.
Do this instead
Ask for confirmation first, or offer an Undo for a few seconds.
Your mission
Milestone 1: list and create
Quick check
In your CMS, which request should update just the title of post 7?
Go deeper
- 1Build create/edit/delete flows against a real or mock backend.
- 2Add a cover-image upload with a preview before submitting.
- 3Implement a draft vs published status toggle.
More projects at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.