PayPulse is a full-stack peer-to-peer payments app inspired by products like PayTM and Venmo. Users can create an account, sign in securely, view their wallet balance, search other users, send money, and review transaction history from a polished React dashboard.
The project is built as a portfolio-grade product demo with a modern fintech interface, cookie-based authentication, Cloudflare Turnstile bot protection, MongoDB persistence, and transaction-safe money transfers.
- Landing page for PayPulse with a clear product story, app preview, and calls to action.
- Signup and signin flows with inline validation, loading states, toast feedback, and password visibility controls.
- Authenticated dashboard with a sticky app bar, personalized greeting, balance card, user directory, and recent transaction history.
- Send-money flow with recipient context, amount validation, loading state, cancel action, and recovery UI when no recipient is selected.
- Responsive dark fintech UI using Tailwind CSS, gradients, glass-style surfaces, visible focus states, and reduced-motion-friendly animations.
- Toast notifications for success, error, loading, logout, and transfer feedback.
- New users automatically receive a starting wallet balance.
- Users can search the user directory by first or last name.
- User search supports pagination and excludes the currently signed-in user.
- Users can transfer money to another PayPulse account.
- Transfers prevent self-payment, invalid recipients, and insufficient-balance transactions.
- Transaction history shows sent and received payments with counterparty names, relative timestamps, status badges, and signed amounts.
- Passwords are hashed with
bcryptbefore being stored. - Authentication uses JWT access and refresh tokens.
- Tokens are sent through HTTP-only cookies instead of being exposed directly to frontend JavaScript.
- Protected backend routes use an authentication middleware that accepts cookie tokens or bearer tokens.
- Signup and signin can be protected by Cloudflare Turnstile.
- Backend validation uses
zodfor signup, signin, and profile update payloads. - CORS is configured for credentialed frontend requests.
- MongoDB models for users, accounts, and transactions.
- Mongoose sessions are used for transfer operations so debiting one account, crediting another, and recording the transaction happen together.
- API responses follow a consistent
success,message, anddatashape across most endpoints. - Transaction records include sender, receiver, amount, status, and timestamps.
- React 18
- Vite
- React Router
- Tailwind CSS
- Axios
- React Hot Toast
- Cloudflare Turnstile via
@marsidev/react-turnstile
- Node.js
- Express
- MongoDB
- Mongoose
- JSON Web Tokens
- bcrypt
- zod
- cookie-parser
- cors
- dotenv
PayPulse/
+-- backend/
| +-- controllers/ # Route handlers for users and accounts
| +-- middlewares/ # Auth and Turnstile middleware
| +-- models/ # User, Account, and Transaction schemas
| +-- routes/ # Express routers
| +-- index.js # Express app and MongoDB connection
| +-- package.json
+-- frontend/
| +-- src/
| | +-- components/ # Appbar, balance, users, forms, transactions
| | +-- pages/ # Home, Signup, Signin, Dashboard, SendMoney
| | +-- App.jsx # Client routes
| | +-- main.jsx
| +-- package.json
+-- DESIGN.md # Visual design system notes
+-- PRODUCT.md # Product positioning and UX goals
+-- Dockerfile # MongoDB replica-set image for transactions
+-- README.md
- Node.js 18 or newer
- npm
- MongoDB
- A MongoDB replica set if you want transfer transactions to work reliably with Mongoose sessions
The included Dockerfile creates a MongoDB image that starts with replica-set support.
git clone <your-repo-url>
cd PayPulse
cd backend
npm install
cd ../frontend
npm installCreate backend/.env:
PORT=4000
DATABASE_URL=mongodb://localhost:27017/paypulse?replicaSet=rs
FRONTEND_URL=http://localhost:5173
ACCESS_TOKEN_SECRET=replace-with-a-long-random-secret
ACCESS_TOKEN_EXPIRY=1d
REFRESH_TOKEN_SECRET=replace-with-another-long-random-secret
REFRESH_TOKEN_EXPIRY=10d
TURNSTILE_SECRET_KEY=
NODE_ENV=developmentTURNSTILE_SECRET_KEY is optional for local development. If it is missing, the backend middleware skips Turnstile verification and logs a warning.
Create frontend/.env:
VITE_BACKEND_URL=http://localhost:4000
VITE_TURNSTILE_SITE_KEY=VITE_TURNSTILE_SITE_KEY is optional locally. If it is not set, the Turnstile widget is not rendered.
Option A: use your own MongoDB replica set.
Option B: build and run the included MongoDB image:
docker build -t paypulse-mongo .
docker run -d --name paypulse-mongo -p 27017:27017 paypulse-mongocd backend
npm run devThe API runs on http://localhost:4000 by default.
cd frontend
npm run devThe app runs on http://localhost:5173 by default.
npm run dev # Start backend with nodemon
npm start # Start backend with node
npm test # Placeholder test scriptnpm run dev # Start Vite dev server
npm run build # Build production assets
npm run preview # Preview production build
npm run lint # Run ESLintAll application API routes are mounted under /api/v1.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/v1/user/signup |
No | Create a user account and initial wallet balance |
POST |
/api/v1/user/signin |
No | Sign in and set access/refresh cookies |
POST |
/api/v1/user/logout |
Yes | Clear stored refresh token and auth cookies |
POST |
/api/v1/user/refresh-token |
Yes | Refresh access token using refresh token |
GET |
/api/v1/user/current-user |
Yes | Return the authenticated user |
PUT |
/api/v1/user/ |
Yes | Update password, first name, or last name |
GET |
/api/v1/user/bulk |
Yes | Search users with filter, page, and limit query params |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/v1/account/balance |
Yes | Return the authenticated user's balance |
POST |
/api/v1/account/transfer |
Yes | Transfer money to another user |
GET |
/api/v1/account/transactions |
Yes | Return sent and received transaction history |
POST /api/v1/user/signup
Content-Type: application/json
{
"username": "aisha@example.com",
"password": "secret123",
"firstName": "Aisha",
"lastName": "Khan",
"turnstileToken": "optional-turnstile-token"
}POST /api/v1/user/signin
Content-Type: application/json
{
"username": "aisha@example.com",
"password": "secret123",
"turnstileToken": "optional-turnstile-token"
}GET /api/v1/user/bulk?filter=rahul&page=1&limit=5
POST /api/v1/account/transfer
Content-Type: application/json
{
"to": "recipient-user-id",
"amount": 500
}username: unique email-style loginpassword: hashed passwordfirstNamelastNamerefreshToken- timestamps
userId: reference toUserbalance: numeric wallet balance
fromUserId: sender user referencetoUserId: receiver user referenceamountstatus:successorfailed- timestamps
- A visitor lands on the PayPulse home page and chooses to sign up.
- Signup validates the form, optionally verifies Turnstile, creates the user, and creates an account balance.
- The user signs in, receiving HTTP-only access and refresh cookies.
- The dashboard loads the user's balance, searchable user directory, and transaction history.
- The user selects another user, enters an amount, and submits a transfer.
- The backend runs the transfer in a MongoDB transaction, updates both balances, records the transaction, and returns success.
- The user returns to the dashboard and can see the updated history.
PayPulse uses a "Night Transit" design direction: a deep slate canvas with a warm orange-to-red pulse for primary actions. The interface emphasizes one clear focal action per screen, readable financial data, and familiar form patterns. More detail lives in DESIGN.md and PRODUCT.md.
- The frontend keeps a small
localStoragelogin flag for client-side UX, but actual authentication is handled by HTTP-only cookies. - MongoDB transactions require replica-set support.
- The backend test script is currently a placeholder.
backend/config.jscontains an older staticJWT_SECRETexport, while the active token generation and verification paths use environment variables.