A blog authoring app built as part of the required projects at The Odin Project Node.js course.
The course requirements for this project are divided into 3 pieces:
- Backend API, which I built as part of the
generic-express-service. - Blog viewer: which I built in
odin-blog-viewer. - Blog author: which I built in this project for authoring/viewing the blog posts.
Here are the requirements from the course website:
We’re actually going to create the backend and two different front-ends for accessing and editing your blog posts. One of the front-end sites will be for people that want to read and comment on your posts, while the other one will be just for you to write, edit and publish your posts. Why are we setting it up like this? Because we can! The important exercise here is setting up the API and then accessing it from the outside.
Important Notes:
- Multiple Authors: I have extended the author website (this project) to accept multiple authors, so any signed-up account can author a blog post.
- Guest Sing-in: I have added the ability to sign in as a guest to facilitate trying the app.
- Backend State Reset: I built these projects to showcase what I am learning in web development, and I don't plan to keep maintaining them; therefore, I added a reset feature within the
generic-express-serviceto periodically delete any non-admin data.
- TypeScript
- Zod for input validation
- React with its Context API
- React Hook Form for controlled form state
- React Testing Library for component testing
- Next.js with SSR and
fetchfor server HTTP requests - TanStack Query for data fetching and caching
- Motion (prev Framer Motion) for animations
- axios for browser HTTP requests
- date-fns for date formatting
- shadcn/ui for UI primitives
- Tailwind CSS for styling
- vitest for running tests
While building this project, I faced several challenges, such as:
-
Authentication with SSR: I wanted the backend to handle auth while still leveraging Next.js SSR. The solution was to introduce a custom Next.js API route that proxies auth requests, maintains a cookie between the Next server and frontend, and uses a Bearer token schema between the Next server and backend. Check out the auth route, auth module, and auth context for implementation details.
-
Reusable Dialog with Form Component: I created a dialog system via context, so I can avoid repeating it on every usage (DRY). However, when placing a reusable form inside a dialog, I needed the dialog to stay open while the form had unsaved changes. To solve this, the form exposes a
shouldUnmountfunction as ref within an imperative handle, and the form's parent passes it to the dialog, which expects it and calls it before closing. Check out the dialog context and, for example, thePostFormfor implementation details. -
Image Uploader & Editor: The backend image upload was already a challenge in the
generic-express-service. On the frontend, I needed to show the selected image immediately and allow deleting the image or positioning it within the image boundaries, while not committing any of these changes until submitting the image form. I built a small custom image toolkit to handle this. Check outImageToolkit,MutableImage,ImageInput, andImageFormfor implementation details. -
Reusable/Customizable Tested Components: I tried hard to build this app consists of multiple reusable/customizable components to be useful for future usage, and to facilitate the app's maintainability.
-
Authentication & Authorization
- Custom solution that bridges SSR in Next.js with backend auth.
- Auth API route in Next.js acts as a middle layer between frontend and backend.
- Auth context on the frontend with cookie-based session between the Next.js server and its frontend.
- Bearer token schema between Next.js and the backend.
- Middleware validation in Next.js.
-
Blog Management
- Create, update, delete, and search posts.
- Infinite scroll powered by TanStack Query.
- Attach images to posts.
-
Comments
- Add, update, and delete comments.
- Infinite loading of comments.
-
User Profiles
- View/Edit profile info (username, bio, avatar).
- Profile-specific post listings.
- delete account.
-
Images
- Upload and store images using the backend service.
- Simple in-app image editor to adjust position.
- Temporarily delete/edit an image until submission.
- Automatic image browser-cache invalidation, for real-time mutations.
-
Reusable Components
- Dialog system via context (global dialogs for forms, confirmations, etc.).
- Dynamic Form system with
react-hook-form, reusable across the app. - Image toolkit for image editing (positioning, deleting).
- Shared UI primitives from
shadcn/ui.
-
UI/UX
- Animated components via Motion (prev Framer Motion).
- Loading skeletons for better perceived performance.
- Responsive, dark/light/system mode.
- Toast notifications for global feedback.
-
Testing
- Component tests with
vitest.
- Component tests with
- Node.js (20 LTS or later)
- Clone of
generic-express-service
-
Clone and set up the backend
Refer to
generic-express-servicefor more details.git clone https://github.com/hussein-m-kandil/generic-express-service.git cd generic-express-service npm install # Refer to `.env.test` to configure .env (DB connection, ports, etc.) # Start the PostgreSQL database npm run pg:up # Build the backend source code npm run build # Start the backend production server npm run start
Make note of the backend base URL, which should be
http://localhost:8080/api/v1. You will need it in the frontend.env. -
Clone, install, and configure the Next.js app
git clone https://github.com/hussein-m-kandil/odin-blog-author.git cd odin-blog-author npm install cp env.sample .env cp env.sample .env.test npm run test -- --run npm run dev
The app will be available at:
http://localhost:3000. -
Useful scripts
- Start dev server:
npm run dev - Build:
npm run build - Run tests:
npm test - Lint:
npm run lint - Type check:
npm run type-check - Start production server:
npm run start
Important Note: A secure cookie is used for authentication between the browser and the Next.js server, hence an
httpsscheme is mandatory. So, connecting to the localhttpserver via local IP address from another device (e.g, mobile phone), won't work as expected, and you will never leave the signin/signup pages even after a successful sign-in. The only solution that I know for this situation is using the--experimental-httpsoption with the Next.js dev server. - Start dev server:
