A modern, full-stack note-taking application with secure authentication and real-time updates
The Notes App is a full-stack web application that allows users to create, manage, and organize their personal notes with rich text and image support. Built with modern web technologies, it provides a seamless user experience across devices with secure authentication and real-time updates.
- Framework: React 19 with Vite for fast development and optimized builds
- State Management: Redux Toolkit for predictable state management
- Styling: TailwindCSS with custom theming and Radix UI components
- Routing: React Router v7 for client-side navigation
- HTTP Client: Axios for API communication
- UI Components: Custom-built components with responsive design
- Runtime: Node.js with Express.js framework
- Database: MongoDB with Mongoose ODM
- Authentication: JWT and OAuth 2.0 (Google)
- File Storage: Cloudinary for image uploads and management
- Email Service: Nodemailer for transactional emails
- Validation: Yup for request validation
- Version Control: Git
- Package Management: npm
- Development Tools: ESLint, Prettier
- Secure registration with email verification
- Password reset functionality
- Social login (Google OAuth)
- Session management with JWT
- Protected routes
- Create, read, update, and delete notes
- Rich text formatting
- Image upload and preview
- Responsive design for all devices
- Real-time updates
- Drag-and-drop image uploads
- Search functionality
- Note categorization
- Dark/Light mode
- Offline support (PWA ready)
- Password hashing with bcrypt
- CSRF protection
- Rate limiting
- Input sanitization
- Secure HTTP headers
- Code splitting
- Lazy loading
- Image optimization
- Efficient state management
- Cached API responses
- Project overview
- Tech stack
- Repository structure
- Environment variables
- Setup & run (development)
- Scripts
- API endpoints (backend)
- Authentication flow
- Frontend overview
- File uploads
- Validation & error handling
- Email verification & OTP
- Deployment notes
- Troubleshooting
- Next steps / improvements
The project consists of two main parts:
server/- Express.js backend (Node.js + MongoDB) exposing REST APIs for user management and todos.client/- React frontend built with Vite that interacts with the backend APIs and provides a UI for signup, login, creating todos with optional images, and password reset.
Features
- Register / login with email & password
- Google OAuth sign-in
- Email verification via token sent to user email
- Password reset via OTP emailed to user
- JWT-based authentication for protected routes
- Create, read, update, delete todos (with optional image upload)
- Multer for image uploads (stored in
uploads/) - Simple session model to prevent duplicate sessions
- Frontend: React 19, Vite, React Router, Redux Toolkit, Redux Persist, Axios, Tailwind (partial), Sonner for toasts
- Backend: Node.js (ES modules), Express, MongoDB/Mongoose, Passport.js (Google OAuth), JWT, Nodemailer (Gmail), Multer, Yup for request validation
Key folders and files:
client/- React appsrc/- application sourceApp.jsx- routes and top-level routermain.jsx- app entry and redux providerredux/- redux slice & storepages/- Signup, Login, Home, CreateTodo, ForgotPassword, VerifyOTP, ChangePassword, AuthSuccesscomponents/- Navbar, ProtectedRoute, UI components
server/- Express backendindex.js- server entryconfig/-database.js,passport.jscontrollers/-user.controller.js,todo.controller.jsmodels/-user.model.js,todo.model.js,session.model.jsroutes/-auth.route.js,user.route.js,todo.route.jsmiddleware/- authentication, multer, registration token verifieremailVerify/-verifyMail.js,sendOtpMail.js, email templateuploads/- directory used by multer to store uploaded images
Create a .env file at the server/ root and set the following variables (example values shown):
- PORT=8000
- MONGO_URI=mongodb://mongo:27017/notes-app
- SECRET_KEY=your_jwt_secret
- CLIENT_URL=http://localhost:5173
- SERVER_URL=http://localhost:8000
- GOOGLE_CLIENT_ID=your_google_client_id
- GOOGLE_CLIENT_SECRET=your_google_client_secret
- MAIL_USER=your.email@gmail.com
- MAIL_PASS=your_gmail_app_password
- CLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name
- CLOUDINARY_API_KEY=your_cloudinary_api_key
- CLOUDINARY_API_SECRET=your_cloudinary_api_secret
Notes:
MAIL_USERandMAIL_PASSare used by Nodemailer to send verification and OTP emails. For Gmail, use an App Password if 2FA is enabled.GOOGLE_CLIENT_*values are required for Google OAuth.- For local development, set callback URL to
http://localhost:8000/auth/google/callback - For production (Vercel), add
https://notes-app-server-puce.vercel.app/auth/google/callbackto authorized redirect URIs in Google Cloud Console. - When deploying to Vercel, set
SERVER_URLenvironment variable to your full server URL (e.g.,https://notes-app-server-puce.vercel.app)
Prerequisites
- Node.js (16+ recommended)
- npm (or yarn)
- MongoDB running locally or a MongoDB Atlas URI
Install dependencies
Open two terminals (one for backend and one for frontend).
Backend (server):
cd "c:\Users\Desktop\Full Stact App\Notes App\server";
npm installFrontend (client):
cd "c:\Users\Desktop\Full Stact App\Notes App\client";
npm installRun the apps
Backend:
cd "c:\Users\Desktop\Full Stact App\Notes App\server";
npm run devFrontend:
cd "c:\Users\Desktop\Full Stact App\Notes App\client";
npm run devThe frontend expects the backend at http://localhost:8000 and CORS is configured in server/index.js for http://localhost:5173.
This will start MongoDB, the server on port 8000 and the built client served by nginx on port 5173.
- server/package.json
npm run dev- start server with nodemonnpm start- start server with node
- client/package.json
npm run dev- start Vite dev servernpm run build- build production frontendnpm run preview- preview built frontend
Base URL: http://localhost:8000
Auth routes
- GET /auth/google - redirect to Google OAuth
- GET /auth/google/callback - Google callback that issues a JWT and redirects to frontend with token in query
- GET /auth/me - Protected; returns the authenticated user's details (requires Authorization: Bearer )
User routes (/api/v1/user)
- POST /api/v1/user/register - Register new user; sends verification email
- POST /api/v1/user/verify-email - Middleware
registrationTokenVerifyexpects a token to verify an email - POST /api/v1/user/login - Login with email & password; returns accessToken & refreshToken
- POST /api/v1/user/logout - Protected; logs out the user and deletes session
- POST /api/v1/user/forgot-password - Send OTP to email for password reset
- POST /api/v1/user/verify-otp/:email - Verify OTP sent to email
- POST /api/v1/user/change-password/:email - Change password after OTP verification
Todo routes (/api/v1/todo)
- POST /api/v1/todo/create - Create todo (protected). Supports image upload (multipart/form-data, field
image). - GET /api/v1/todo/get - Fetch all todos for authenticated user
- DELETE /api/v1/todo/delete/:todoId - Delete a todo owned by the authenticated user
- PUT /api/v1/todo/update/:todoId - Update a todo (supports new image upload)
Authentication: Protected endpoints require header Authorization: Bearer <accessToken>.
- Email signup: User registers via
/api/v1/user/register. Server creates user and issues a short-lived token stored on user and emails a verification link (viaverifyMail). - Email verification: User clicks link -> frontend
Verifyroute calls/api/v1/user/verify-emailwhich uses the token to verify the account. - Login: User logs in with
/api/v1/user/loginand receivesaccessTokenandrefreshToken. Backend also tracks sessions viaSessionmodel. - Google OAuth: Frontend opens
/auth/google. After successful OAuth, backend uses Passport to create/find user and then signs a JWT and redirects to frontend's/auth-success?token=.... - Protected routes: Frontend stores
accessToken(currently in localStorage inAuthSuccess.jsx) and sends it inAuthorizationheader for protected API calls.
Security notes:
- JWT secret is in
SECRET_KEYenv variable; ensure it's strong in production. - Storing tokens in localStorage is vulnerable to XSS. For production consider HttpOnly cookies for access/refresh tokens.
- Routes are defined in
client/src/App.jsx. AuthSuccess.jsxreads the token passed during OAuth flow and stores it inlocalStorage, then fetches/auth/meto populate the Reduxuserstate.ProtectedRoute.jsxcomponent guards private pages likeCreateTodo.redux/authSlice.jsholds minimal user state and loading flag. Redux Persist is configured to persist the store across refreshes (seeclient/src/redux/store.js).
UI components are in client/src/components/ui/ and include form elements, buttons, alerts, and other small building blocks.
- Backend uses Cloudinary for image storage and management.
- Image upload configuration is in
server/config/cloudinary.js. - Supported file types: jpeg/jpg/png/gif (limit 10 MB).
- Images are automatically optimized and served through Cloudinary's CDN.
- When updating a todo with a new image, the old image is automatically deleted from Cloudinary.
- Image URLs are stored in the todo document as Cloudinary secure URLs.
- Request bodies for user registration and todo creation are validated using
yupschemas inserver/validators. - Global error handler in
server/index.jslooks forLIMIT_FILE_SIZEerror from multer and returns a friendly 400 response.
verifyMail.jsrenders an email templatetemplate.hbsand sends it via Nodemailer (configured for Gmail in.env).sendOtpMail.js(present inemailVerify/) sends OTPs for password reset.- OTP is stored on the
Usermodel (otpandotpExpiry) and expires after 10 minutes.
- Ensure
CLIENT_URLis set to the production frontend URL. - Set
callbackURLinserver/config/passport.jsto the production backend/auth/google/callbackURL. - The project now supports env-driven base URLs: the frontend reads
VITE_API_URL(seeclient/.env.example) and the server usesSERVER_URL(seeserver/.env.example). Update these values in production. - For production, prefer storing tokens in secure HttpOnly cookies and enable HTTPS.
- Use cloud storage or a CDN for uploaded images rather than local disk.
- Use proper MAIL service credentials; consider transactional email services (SendGrid, Mailgun) for better deliverability.
Common Issues and Solutions:
-
Google OAuth Errors:
- Error 400 (redirect_uri_mismatch): Make sure to add both development and production callback URLs in Google Cloud Console:
- Local:
http://localhost:8000/auth/google/callback - Production:
https://notes-app-server-puce.vercel.app/auth/google/callback
- Local:
- Ensure
SERVER_URLenvironment variable is set correctly in production
- Error 400 (redirect_uri_mismatch): Make sure to add both development and production callback URLs in Google Cloud Console:
-
Email Verification Issues:
- Email sending fails: Verify
MAIL_USERandMAIL_PASSand use app passwords for Gmail with 2FA - OTP not received: Check spam folder and verify email templates in
emailVerify/
- Email sending fails: Verify
-
Database Connection:
- MongoDB connection errors: Check
MONGO_URIformat and ensure MongoDB is running - For Atlas: Verify IP whitelist and database user credentials
- MongoDB connection errors: Check
-
CORS and API Issues:
- CORS errors: Confirm
CLIENT_URLmatches the frontend URL (Vite dev server in development) - API 404 errors: Check if
SERVER_URLis set correctly in frontend environment
- CORS errors: Confirm
-
File Upload Issues:
- Upload failures: Verify Cloudinary credentials (CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET)
- Upload size errors: Verify file size is under 10MB limit
- Missing images: Check if Cloudinary URLs are properly stored and accessible
- Image not displaying: Ensure the Cloudinary secure URL is being used
- Move tokens to HttpOnly cookies and implement refresh token rotation.
- Add rate limiting and request throttling to sensitive endpoints.
- Extract configuration constants and centralize error handling.
- Add unit & integration tests (Jest / Supertest) for backend endpoints.
- Implement image optimization and transformation using Cloudinary's advanced features.
- Improve frontend form validation and UX for error states.
We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
- Reporting a bug
- Discussing the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer
We use GitHub to host code, track issues and feature requests, and accept pull requests.
All code changes happen through pull requests. Pull requests are the best way to propose changes to the codebase.
- Fork the repo and create your branch from
main. - If you've added code that should be tested, add tests.
- If you've changed APIs, update the documentation.
- Ensure the test suite passes.
- Make sure your code lints.
- Issue that pull request!
In short, when you submit code changes, your submissions are understood to be under the same MIT License that covers the project.
We use GitHub issues to track public bugs. Report a bug by opening a new issue.
A good bug report includes:
- A quick summary and/or background
- Steps to reproduce
- Be specific! Give sample code if you can.
- What you expected would happen
- What actually happens
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
- 2 spaces for indentation
- Follow the existing code style
- Run
npm run lintto automatically format your code
By contributing, you agree that your contributions will be licensed under its MIT License.
MIT License
Copyright (c) 2025 Notes App
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.