OpenNotes is a full-stack blogging platform where users can create, share, and discover articles with a rich-text editor and secure authentication.
- Please Visit OpenNotes !
- Key Features of OpenNotes
- TECH STACK
- Database Model
- ROUTES
- npm packages in Server Side
- Technologies Used
- Installation and Setup Instructions
- Deployment
- User registration and login with email and password.
- JWT authentication with access and refresh tokens.
- HTTP-only cookies for authentication.
- Logout functionality.
- Email verification.
- Forgot-password and reset-password flows.
- Change password for logged-in users.
- Protected routes and role-based access.
- Redirect users to their intended page after login.
- View and edit personal information.
- Upload or update a profile photo.
- Manage name, username, biography, phone, and country.
- Add website and Instagram links.
- Display post, follower, and following counts.
- Create, view, edit, and delete blog posts.
- Write formatted content using the Tiptap editor.
- Support headings, lists, links, quotes, and code blocks.
- Apply text alignment, text colours, and highlights.
- Upload and replace cover images.
- Assign categories and add multiple tags.
- Save posts as drafts or publish them.
- Set public or private visibility.
- Access posts through unique, readable slugs.
- Display reading time and publication dates.
- Browse the latest posts.
- View individual posts and author details.
- Navigate paginated post listings.
- Select the number of posts per page.
- View total posts and the current results range.
- Preserve pagination in the URL for sharing and navigation.
- Personal dashboard for managing posts.
- Edit posts directly from dashboard actions.
- Manage draft and published content.
- Support User, Admin, and Super Admin roles.
- Manage user accounts, roles, and account statuses.
- Restrict actions according to permissions.
- Responsive layouts for desktop and mobile.
- Reusable shadcn/ui components.
- Client-side and server-side validation.
- Image type and size validation.
- Image previews before uploading.
- Loading indicators and disabled submission controls.
- Helpful validation, API-error, and empty-state messages.
- PostgreSQL storage managed through Prisma.
| Layer | Technologies | Purpose |
|---|---|---|
| Frontend | Next.js(App Router), React, TypeScript | Pages, components, routing, and server actions |
| Styling | Tailwind CSS, shadcn/ui | Styling and reusable UI components |
| Forms | React Hook Form, Zod | Form handling and input validation |
| API communication | Fetch API, JSON, FormData | Sending requests, structured data, and image uploads |
| Rich-text editor | Tiptap | Writing blog content and storing it as JSON |
| Date formatting | date-fns | Displaying readable dates |
| Icons | Lucide React | Interface icons |
| Deployment | Vercel | Deployment platform you’re configuring |
| Layer | Technologies | Purpose |
|---|---|---|
| Backend | Node.js, Express.js, TypeScript | REST APIs and business logic |
| Database | PostgreSQL | Storing users, posts, categories, and comments |
| ORM | Prisma | Database queries, models, and migrations |
| Authentication | JWT, bcryptjs, HTTP-only cookies | Access/refresh tokens, password hashing, and cookie-based authentication |
| Middleware | cookie-parser, CORS | Reading cookies and allowing frontend API requests |
| Image uploads | Multer, multer-storage-cloudinary, Cloudinary | Receiving, storing, and serving images |
| Deployment | Vercel | Deployment platform you’re configuring |
Fields & Validation:
Required indicates whether a value must be supplied when creating a user. Fields with defaults or automatic values can be omitted.
| Field | Type | Required | Details |
|---|---|---|---|
| id | string | No | Primary key; automatically generated UUID with uuid() |
| string | Yes | Unique email address; email format validation is not defined in this model | |
| password | string | No | Nullable password field; store a password hash, not plain text |
| name | string | No | User’s name; nullable |
| username | string | No | Unique username when provided; nullable |
| profilePhoto | string | No | Profile photo reference or URL; nullable |
| boi | string | No | User biography; nullable; field name preserved as boi |
| role | Role | No | Defaults to USER; allowed values depend on the Role enum definition SUPER_ADMIN, ADMIN, USER |
| phone | string | No | Phone number; nullable |
| country | string | No | User’s country; nullable |
| status | UserStatus | No | Defaults to ACTIVE; allowed values depend on the UserStatus enum definition ACTIVE, INACTIVE, BLOCKED |
| isVerified | boolean | No | Defaults to false; indicates whether the user is verified |
| website | string | No | User’s website; nullable |
| string | No | Instagram reference; nullable | |
| followersCount | integer | No | Followers count; defaults to 0 |
| followingCount | integer | No | Following count; defaults to 0 |
| postsCount | integer | No | Post count; defaults to 0 |
| lastLoginAt | DateTime | No | Last login timestamp; nullable |
| deletedAt | DateTime | No | Soft deletion timestamp; nullable |
| createdAt | DateTime | No | Automatically defaults to the current timestamp with now() |
| updatedAt | DateTime | No | Automatically managed by Prisma using @updatedAt |
Relations:
| Field | Type | Required | Details |
|---|---|---|---|
| posts | BlogPost[] | No | Related blog posts; can be empty |
| comments | Comment[] | No | Related comments; can be empty |
| likes | Like[] | No | Related likes; can be empty |
| bookmark | Bookmark[] | No | Related bookmarks; can be empty |
| following | Follower[] | No | Follower records using the named relation following |
| followers | Follower[] | No | Follower records using the named relation followers |
| userId | Notification[] | No | Notification records using the named relation userId; a relation list, not a scalar ID |
| senderId | Notification[] | No | Notification records using the named relation senderId; a relation list, not a scalar ID |
| analytics | Analytics[] | No | Related analytics records; can be empty |
Fields & Validation:
| Field | Type | Required | Details |
|---|---|---|---|
| id | string | No | Primary key; automatically generated UUID using uuid() |
| categoriesName | string | Yes | Category name; no uniqueness or minimum length constraint defined |
| icon | string | No | Optional category icon reference; nullable |
| categoriesPostsCount | integer | No | Number of posts in the category; defaults to 0 |
| createdAt | DateTime | No | Creation timestamp; defaults to the current time using now() |
| updatedAt | DateTime | No | Timestamp automatically managed by Prisma using @updatedAt |
Relations:
| Field | Type | Required | Details |
|---|---|---|---|
| posts | BlogPost[] | No | Related blog posts; can be empty list |
Fields & Validation:
Required indicates whether a value must be supplied when creating a blog post. Fields with defaults or automatic values can be omitted. Required foreign keys can also be populated through their corresponding relation inputs.
| Field | Type | Required | Details |
|---|---|---|---|
| id | string | No | Primary key; automatically generated UUID with uuid() |
| title | string | Yes | Blog post title; no minimum or maximum length defined in this model |
| slug | string | No | Unique slug when provided; nullable; no automatic slug generation defined |
| content | Json | Yes | JSON content for an editor such as Tiptap, Slate, BlockNote, Editor.js, or Draft.js; editor-specific structure is not validated by this model |
| coverImage | string | No | Cover image reference or URL; nullable |
| categoryId | string | Yes | Foreign key referencing Categories.id; can be populated through the category relation |
| isFeatured | boolean | No | Defaults to false; intended for admin-only changes, which must be enforced in application logic |
| tags | string[] | No | List of tag strings; can be empty; no explicit @default declared |
| status | BlogPostStatus | No | Defaults to PUBLISHED; other allowed values depend on the enum definition DRAFT, PUBLISHED |
| visibility | BlogPostVisibility | No | Defaults to PUBLIC; other allowed values depend on the enum definition PUBLIC, PRIVATE |
| views | integer | No | View count; defaults to 0 |
| readTime | integer | No | Reading duration; nullable; unit is not specified in this model |
| likesCount | integer | No | Like count; defaults to 0 |
| commentsCount | integer | No | Comment count; defaults to 0 |
| sharesCount | integer | No | Share count; defaults to 0 |
| authorId | string | Yes | Foreign key referencing User.id; can be populated through the author relation |
| createdAt | DateTime | No | Automatically defaults to the current timestamp with now() |
| updatedAt | DateTime | No | Automatically managed by Prisma using @updatedAt |
| publishedAt | DateTime | No | Publication timestamp; nullable; not automatically set by this model |
Relations:
| Field | Type | Required | Details |
|---|---|---|---|
| category | Categories | Yes | Related category; linked through categoryId referencing Categories.id |
| comments | Comment[] | No | Related comments; can be empty |
| likes | Like[] | No | Related likes; can be empty |
| bookmarks | Bookmark[] | No | Related bookmarks; can be empty |
| Notifications | Notification[] | No | Related notifications; can be empty; field capitalization preserved as Notifications |
| analytics | Analytics[] | No | Related analytics records; can be empty |
| author | User | Yes | Related author; linked through authorId referencing User.id |
Fields & Validation:
Required indicates whether a value must be supplied when creating a comment. Fields with defaults or automatic values can be omitted. Required foreign keys can also be populated through their corresponding relation inputs.
| Field | Type | Required | Details |
|---|---|---|---|
| id | string | No | Primary key; automatically generated UUID with uuid() |
| content | string | Yes | Comment text; no minimum or maximum length defined in this model |
| likesCount | integer | No | Like count; defaults to 0 |
| isEdited | boolean | No | Defaults to false; indicates whether the comment has been edited |
| authorId | string | Yes | Foreign key referencing User.id; can be populated through the author relation |
| postId | string | Yes | Foreign key referencing BlogPost.id; can be populated through the commentPost relation |
| createdAt | DateTime | No | Automatically defaults to the current timestamp with now() |
| updatedAt | DateTime | No | Automatically managed by Prisma using @updatedAt |
Relations:
| Field | Type | Required | Details |
|---|---|---|---|
| author | User | Yes | Comment author; linked through authorId referencing User.id |
| commentPost | BlogPost | Yes | Blog post associated with the comment; linked through postId referencing BlogPost.id |
| likes | Like[] | No | Related likes; can be empty |
| Route | Method | Description |
|---|---|---|
/api/v1/auth/register |
POST | Create a new USER |
/api/v1/auth/login |
POST | USER Login |
/api/v1/auth/logout |
POST | USER Logout |
/api/v1/auth/refresh-token |
POST | Get New Refresh Token |
/api/v1/auth/change-password |
POST | Change User Password |
/api/v1/auth/forgot-password |
POST | Forgot Password |
/api/v1/auth/reset-password |
POST | Reset User Password |
| Route | Method | Description |
|---|---|---|
/api/v1/user/me |
GET | GET USER Details |
/api/v1/user/:userId |
GET | GET USER Details with userId |
/api/v1/user/:userId |
PATCH | Update USER Details with userId |
| Route | Method | Description |
|---|---|---|
/api/v1/categories |
GET | GET All Category |
/api/v1/categories/:categoryId |
GET | GET a single Category with categoryId |
/api/v1/categories |
POST | POST a Category - only ADMIN AND SUPER_ADMIN can post a Category |
| Route | Method | Description |
|---|---|---|
/api/v1/post?page=1&limit=10 |
GET | GET All POST |
/api/v1/post?search=nextjs&tag=react |
GET | GET All POST |
/api/v1/post?authorId=USER_UUID |
GET | GET All POST |
/api/v1/post/:postId |
GET | GET A Single POST with postId |
/api/v1/post/slug/:slug |
GET | GET A Single POST with slug |
/api/v1/post |
POST | CREATE A Single POST |
/api/v1/post/:postId |
PATCH | UPDATE A Single POST |
| Route | Method | Description |
|---|---|---|
/api/v1/post/:postId/comments |
GET | GET All Comments of a post |
/api/v1/post/:postId/comments |
POST | CREATE a Comments of a post |
/api/v1/comments/:commentId |
PATCH | UPDATE a Comment |
- Use TypeScript to add static typing, catch errors early, and make JavaScript code easier to maintain.
- Use ts-node-dev to run TypeScript applications and automatically restart them when code changes during development.
- Use node.js for server-side scripting and building web applications.
- Uses express to build web applications and APIs easily with routing, middleware, and request handling.
- Uses cors for enabling controlled access to resources from different origins in web applications.
- Uses dotenv environment variables from a .env file into process.env for secure configuration management.
- Uses cookie-parser in Express to parse and manage cookies from incoming client requests easily.
- Uses JWT to securely transmit user authentication data between client and server in web applications.
- Use bcryptjs to securely hash passwords and verify entered passwords against stored hashes during login.
- Use Multer to handle file uploads, such as images, in Express applications.
- Use Cloudinary to upload, store, optimize, transform, and deliver images and videos from the cloud.
- Use multer-storage-cloudinary to automatically store files uploaded through Multer in Cloudinary.
- Use Nodemailer to send emails, including verification links, password resets, and notifications, from Node.js applications.
- Use PostgreSQL to store, organize, and query relational data, including users, posts, comments, and transactions.
- Use Prisma to interact with databases through type-safe queries, define data models, and manage migrations.
- Use Zod to define validation schemas, validate incoming data, and infer TypeScript types automatically.
- Uses Vercel for deploying, hosting, and scaling frontend web applications with speed, simplicity, and automation.
Follow the steps below to set up the OpenNotes application locally:
git clone https://github.com/Arman3747/OpenNotes-Server.git
git clone https://github.com/Arman3747/OpenNotes-Client.gitcd OpenNotes-Client
npm installCreate a .env.local file in the root of the client folder and add the following:
#BASE URL
BASE_API_URL=https://your-backend-domain/api/v1
NEXT_PUBLIC_BASE_API=https://your-backend-domain/api/v1
#jwt
JWT_ACCESS_SECRET = your_jwt_access_secret
JWT_REFRESH_SECRET = your_jwt_refresh_secret
Then start the client:
npm run devcd OpenNotes-Server
npm installCreate a .env file in the root of the server folder and add the following:
# Server
PORT="5000"
NODE_ENV="development"
#DataBase
DATABASE_URL="postgresql://postgres:postgres_password@localhost:5432/db_name?schema=public"
#BCRYPT
SALT_ROUND=your_bcrypt_salt_round
#JWT
JWT_ACCESS_SECRET = your_jwt_access_secret
JWT_ACCESS_EXPIRES = your_jwt_access_expires_eg_1d
JWT_REFRESH_SECRET = your_jwt_refresh_secret
JWT_REFRESH_EXPIRES = your_jwt_refresh_expires_eg_30d
JWT_RESET_PASS_SECRET = your_jwt_reset_secret
JWT_RESET_PASS_EXPIRES = your_jwt_reset_expires_eg_10m
#RESET_PASS_LINK
RESET_PASS_LINK=http://localhost:3000/resetPassword
#Email sender
EMAIL=your_email_address
app_pass=smtp_password
#CLOUDINARY
CLOUDINARY_CLOUD_NAME=your_cloudinary_name
CLOUDINARY_API_KEY=your_cloudinary_api_key
CLOUDINARY_API_SECRET=your_cloudinary_api_secret
Then start the server:
npm run dev- Push the code to GitHub.
- Connect your repository to Vercel.
- Set environment variables in Vercel dashboard.
- Deploy the site. Live URL will be generated automatically.
