Stop 22 · Build Backend Portfolio ProjectsProjects · 2/14

Blogging Platform API

Part of the Backend Developer Roadmap

In short

A blog backend sounds basic, but drafts, slugs, tags, comments, permissions and search add up to a properly real API. It's the perfect project to show clean design end to end.

A fuller blogging backend — posts, comments, tags, drafts, and search — going beyond the earlier Blog API practice project into something portfolio-ready.

The purple blob editor places a finished magazine on the published pile, with drafts and a red pen nearby.

Think of it like this

The college magazine office

Writers submit drafts, the editor publishes the good ones, readers leave comments, and only the original writer can edit their own piece. Your blog API runs this same office, just with HTTP instead of paper.

Watch it happen

Build plan, one layer at a time

Schema
Auth
Posts CRUD
Comments
Search
Deploy

1/6Design tables first: users, posts, tags, post_tags and comments, with foreign keys and indexes.

The big ideas

What you need to know

Drafts and publishing

A status field plus a publishedAt date. Public endpoints only return published posts.

Unique slugs

Turn "My First Post" into my-first-post, with a unique index and a suffix like -2 when it clashes.

Ownership checks

Logged in isn't enough. Only the author, or an admin, can edit or delete a post.

Pagination everywhere

Lists of posts and comments must be paginated, with a sensible default and a maximum page size.

See it in code

Walk through the code

ts
1app.patch("/posts/:id", requireAuth, async (req, res) => {2  const post = await db.post.findUnique({ where: { id: req.params.id } });3  if (!post) return res.status(404).json({ error: "Post not found" });4  if (post.authorId !== req.user.id) {5    return res.status(403).json({ error: "You can only edit your own posts" });6  }7  const { title, body, status } = req.body; // validate these with zod first8  const updated = await db.post.update({9    where: { id: post.id },10    data: {11      title, body, status,12      publishedAt: status === "published" && !post.publishedAt ? new Date() : post.publishedAt,13    },14  });15  res.json(updated);16});

1/3Load the post. Missing means 404.

A Prisma-style update with the two checks every edit endpoint needs: does it exist (404), and is it yours (403).

Save yourself the pain

Common mistakes

Oops

Checking only that the user is logged in before editing.

Do this instead

Also check post.authorId matches the user. Otherwise anyone can edit anyone's post by changing the ID.

Oops

Returning every post in one response.

Do this instead

Paginate from day one, with ?limit and a cursor, and cap the limit at something like 50.

Your mission

Milestone 1: posts with drafts

~45 min

Quick check

A logged-in user tries to edit someone else's post. Which status code fits best?

Go deeper

  1. 1Add full-text search over post content using your database's search features.
  2. 2Implement a draft/published workflow with scheduled publishing.
  3. 3Add proper pagination, filtering by tag, and related-posts logic.

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.

Download
InsideEdge