Skip to content

Repository files navigation

ADK DEV

BlogNest

A full stack blogging platform: write in a rich text editor, publish or keep it a draft, and let readers comment, reply, like, bookmark and follow.

Next.js React Prisma MongoDB
NextAuth Zod 50 e2e checks MIT License



Developer documentation · Features · Getting started

Dark mode · View this page in light mode


Contents


Why this project matters

A blog is the standard "full stack" exercise, and most versions of it stop at create, read, update and delete over a posts table. The parts that make a blog actually usable are the ones that get skipped, and they are where the interesting decisions live.

A draft has to be genuinely invisible: not just filtered from listings, but returning 404 to anyone who guesses the URL, while still loading for its author in the editor. A published permalink has to survive its title being edited, or every link anyone shared breaks. A rich text editor writes HTML into a database that later feeds dangerouslySetInnerHTML, so there is exactly one place the sanitiser can go and exactly one way to get it wrong. A comment thread needs a depth rule, because "reply to a reply to a reply" has no natural end. Deleting a post on MongoDB means deleting its replies before its comments, because there are no cascades to lean on.

BlogNest is built around getting those right, and the reasoning for each is written down in DEVDOC.md rather than left in the code for the next person to reverse engineer.

Where it came from

It started as the standard Next.js blog tutorial and kept going past the point where the tutorial stops.

The turns that changed it were all the same shape: a feature that looks like one field turns out to be a rule. Adding drafts meant deciding what a draft URL does for a stranger. Adding an editor meant deciding where HTML gets sanitised, and the answer is on the way in, once, rather than on every render. Adding likes and comment counts meant deciding whether to count rows on every request or denormalise a counter and keep it honest, and the counter won because sorting by "most discussed" is a query you cannot write against a count you compute in application code.

The result is a blog you can actually run, with the awkward cases handled rather than avoided.

Screenshots

Every image is a real 1440x900 viewport render against a seeded local instance. This page shows dark mode; the same gallery in light mode is at README-light.md.

Home page with a featured story, category strip and recent posts

Home
Featured story, categories, recent posts and a most-read sidebar.

The full library with search, category filter and sort

All posts
Search across titles, body and tags, filter and sort.

An article with author byline, like, save and share

Article
Reading time, views, and like, save and share for the reader.

A comment thread with a nested reply

Discussion
Comments with one level of threaded replies, edit and delete.

The rich text editor with a cover image and publish controls

Editor
Rich text, cover image, category and tags. Publish or save a draft.

The author dashboard with counts and drafts in progress

Dashboard
Published, drafts, views, likes, comments, followers, bookmarks.

Categories page with post counts and all tags

Categories
Six subjects with counts, and every tag in use.

An author profile with bio, counts and published posts

Author profile
Bio, follower counts and everything they have published.

Responsive layout

Each image is its own device viewport. The header collapses to a menu, the grid drops to one column, and the sidebar moves below the content.

Home at 390px wide

Home
390 x 844

All posts at 390px wide

All posts
390 x 844

Home at 820px wide

Home
820 x 900

Features

Reading

  • Home page with a featured story, category strip, recent posts and a most-read sidebar
  • Full library at /blog: search titles, body and tags, filter by category, sort four ways
  • Search-as-you-type in the header, showing the top six matches before you press enter
  • Category pages, tag pages and author profiles, each paginated
  • Related posts under every article, same category first and shared tags second
  • Reading time, views, likes and comment count on every card and article
  • RSS feed at /feed.xml, plus sitemap.xml and robots.txt

Writing

  • Rich text editor: headings, bold, italic, underline, strikethrough, quotes, code blocks, lists, links and inline images
  • Cover image by upload or by pasting a URL you already host
  • Category, up to eight tags, and an optional excerpt generated from the body if left blank
  • Publish now or save as a draft; a draft URL returns 404 to everyone but its author
  • Unsaved new posts autosave to your browser, so a refresh does not lose the draft
  • Unpublishing moves a post back to drafts without losing its comments or likes

Discussion and reactions

  • Comment on any published post, and reply once deep into a thread
  • Edit or delete your own comments; admins can moderate anyone's
  • Like a post, with a public count that drives most-liked ordering
  • Bookmark a post privately, collected in your dashboard
  • Follow an author from their profile or from any byline

Your account

  • Email and password, or GitHub and Google when the site owner has configured them
  • Dashboard with published, drafts, views, likes, comments received, followers and bookmarks
  • Editable profile: name, handle, bio, website and picture. Your handle is your public URL
  • Change your password, or set one for the first time after signing up through a provider
  • Delete your account, which removes your posts, comments, likes, bookmarks and uploads

Everywhere

  • Light and dark themes, remembered per browser, with no flash of the wrong one on load
  • Responsive from phone to desktop
  • Keyboard reachable, with a skip link, focus rings and labelled controls

Writing and publishing a post

Write in the header, or New post from the dashboard.

Give it a title, write the body, pick a category and up to eight tags. Add a cover image by uploading one or pasting a URL. Leave the excerpt blank and one is generated from the first part of the body.

Then either Publish or Save as draft. A draft is visible only to you and to admins: it is excluded from every listing, the feed and the sitemap, and its URL returns 404 to anyone else. Editing a draft's title also changes its slug; once published, the slug is frozen so shared links keep working.

If you close the tab before saving a new post, the draft is restored from your browser next time.

Commenting

Comments are open on any published post to anyone signed in. Replies go one level deep: a reply to a reply attaches to the same root, because an unbounded thread has no sensible layout at 390px wide.

You can edit or delete your own comments. Deleting a comment that has replies takes the replies with it, and the post's comment count is adjusted by the number of rows that actually go away rather than by one.

Liking, saving and following

Like is public and drives the most-liked ordering. Save is a private bookmark, collected under Dashboard → Bookmarks. Follow an author from their profile or from any byline, and their follower count updates immediately.

Your dashboard

Dashboard shows published count, drafts, total views, likes, comments received, followers and bookmarks saved.

My posts is the table: per-post numbers with inline publish, unpublish, edit and delete. Bookmarks is what you saved. Settings holds your profile, your handle, and the password form.

Your handle is your public URL at /authors/<handle>, so changing it changes where your profile lives.

Searching

The header search shows the top six matches as you type. Press enter for the full library, where you can search titles, body text and tags together, filter by category, and sort by newest, oldest, most read or most discussed.

Roles

Role Can do
Visitor Read published posts, search, browse categories, tags and author profiles, subscribe to the feed
User Everything a visitor can, plus write, publish, edit and delete their own posts, comment, reply, like, bookmark and follow
Admin Everything a user can, plus edit or delete any post, moderate any comment, and create categories

An account becomes an admin by having its email listed in ADMIN_EMAILS at the time it signs up.

The post lifecycle

                   Save as draft            Publish
   (writing)  --------------------->  DRAFT  --------->  PUBLISHED
                                        ^                    |
                                        +--------------------+
                                             Unpublish
  • Draft is visible only to its author and to admins. No publishedAt, excluded from every listing, feed and sitemap, and its URL returns 404 to anyone else. Editing the title also changes the slug.
  • Published is listed, searchable, indexable and open for comments. publishedAt is stamped once and never rewritten, so unpublishing and republishing does not move the post to the top of the page. The slug is frozen so existing links keep working.
  • Deleted removes the post along with its comments, replies, likes and bookmarks.

Getting started

You need Node 18 or newer and a MongoDB replica set. Prisma needs a replica set for transactions; Atlas gives you one by default, and DEVDOC.md has the local Docker one-liner.

git clone https://github.com/Dileepadari/BlogNest.git
cd BlogNest
npm install
cp .env.example .env      # set DATABASE_URL and NEXTAUTH_SECRET
npm run db:push           # create the collections and indexes
npm run db:seed           # 4 authors, 12 posts, comments, likes, bookmarks
npm run dev               # http://localhost:3000

Demo accounts

npm run db:seed creates four authors with a term of posts, comments, likes, bookmarks and follows between them.

Role Email Password
Admin demo@example.com DemoPass123!
User maya@example.com DemoPass123!
User tomas@example.com DemoPass123!
User reader@example.com DemoPass123!

Every address is under example.com, which RFC 2606 reserves for exactly this.

End to end checks

npm run dev          # in one terminal
./scripts/e2e.sh     # 50 checks against the real HTTP API

The script signs in through NextAuth, exercises the guards, writes and deletes its own throwaway account, and is safe to run repeatedly. Do not point it at production.

Contributors

Dileep Adari
Dileep Adari

Author and maintainer

Contributing

Issues and pull requests are welcome on the repository.

Before opening a PR, run what CI runs:

npm run lint
npm run build
./scripts/e2e.sh     # needs a running instance and a seeded database

Conventions: single-line commit messages, no em dashes and no literal emoji anywhere, and update DEVDOC.md in the same change if you add a route, a model field or an environment variable.

Two rules worth knowing before touching the data layer. HTML is sanitised on the way in, once, in the API route, so dangerouslySetInnerHTML reads only what the sanitiser already passed. And on MongoDB an absent field and a null one are different states, so a filter meaning "has no parent" has to say both.

License

MIT © Dileep Adari

About

Developed a blogging platform with user authentication, CRUD posts, and comments.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages