Community-centered peer support platform connecting people through shared life experiences.
KindredPal helps people find their people β through support groups, local meetups, and genuine connections built on common values, life stages, and challenges. Available on web and iOS.
| Surface | Technology | Hosting |
|---|---|---|
| iOS Mobile | React Native (Expo) | TestFlight |
| Web App | React 18, React Router | Vercel |
| Backend API | Node.js, Express | Railway |
| Database | MongoDB Atlas, Mongoose | Atlas Cloud |
| Real-time | Socket.IO | Railway |
| Media | Cloudinary | Cloud |
| Resend | Cloud |
- Discover β Browse public groups by category, keyword, city, state, or distance
- Create β Start a group with name, category, location, and privacy setting
- My Groups β Dedicated tab showing groups you've joined or created
- Group Chat β Real-time messaging for group members via Socket.IO
- Members β View all members, send connection requests, or message directly
- Invitations β Admins can invite connections to private groups
- Edit & Delete β Group admins can edit details; creators can delete
- Send connection requests to people you share a group with
- Accept, decline, or ignore incoming requests
- View your connections list with profile photos and bios
- Message any connection directly
- Navigate to a connection's full profile
- Create in-person or virtual meetups and invite your connections
- RSVP Going / Maybe / Not Going
- Guest list with organizer shown first
- Email and push notifications for new invites
- My Groups widget on Meetups tab refreshes on every focus
- Real-time direct messages between connected users
- Group chat per community group
- Unread badge counts updated every 30 seconds and on app foreground
- Profile photo (Cloudinary upload)
- Life stage, family situation, core values (up to 3)
- Faith, political beliefs, bio, location
- Push notification preferences
- Expo push notifications for meetup invites and new connections
- Email notifications via Resend for group invites, meetup invites, password reset
- Tab bar badges for messages, meetup invites, group invites, connection requests
| π€² Caregiver Support | πΏ Grief & Loss | π Sober & Clean Living |
| πΆ New Parent Support | ποΈ Chronic Illness Support | π§ Anxiety & Mental Wellness |
| ποΈ Veteran Support | π» Senior Wellness | π Loneliness & Social Connection |
| π± Divorce Recovery | π Faith & Spiritual Support | π Life Transitions |
| ποΈ Trauma Recovery | π Cancer Support | π¨βπ§ Single Parent Support |
| β Addiction Recovery | π¦ Autism & Special Needs Families | π Singles Social Support |
| π Married No Kids | πΌ Career Change Support | π° Financial Recovery |
| π Sports & Fitness | π― Local Activity Groups |
Root Stack Navigator
βββ Login / Signup
βββ MainTabs (Bottom Tab Navigator)
β βββ Groups β Discover + My Groups tabs
β βββ Connections β My connections + pending requests
β βββ Messages β Direct message conversations
β βββ Meetups β Upcoming meetups + My Groups widget
β βββ Profile β Edit profile, settings
βββ GroupDetail
βββ CreateGroup
βββ MemberProfile
βββ UserProfile
βββ Chat
βββ MeetupDetails
βββ EditProfile
βββ Preferences
βββ BlockedUsers
βββ WebView
KindredPal uses local React state with an optimistic-update + route params pattern:
- Optimistic updates β UI updates instantly from local data, then a delayed server fetch confirms after 1β1.5s
- Route params for cross-screen updates β after create/edit/delete, screens navigate back with
newGroup/updatedGroup/deletedGroupIdparams;GroupsScreenpatches both Discover and My Groups lists immediately useFocusEffectβ every tab screen re-fetches on focus so data stays currentfiltersRefpattern β filter state lives in auseRefsofetchGroupshas stable[]deps and never recreates
Screens inside MainTabs cannot directly reach root stack screens with plain navigation.navigate(). Use either:
// From a tab screen β root stack screen
navigation.getParent()?.navigate("MemberProfile", { userId });
// From a root stack screen β back to a tab with params
navigation.navigate("MainTabs", {
screen: "Groups",
params: { updatedGroup: {...}, timestamp: Date.now() },
});Mobile React Native (Expo SDK)
React Navigation v6 (native stack + bottom tabs)
React Native Paper, Lucide React Native
Expo SecureStore, Expo Notifications
Web React 18, React Router
CSS Modules, Lucide React
Backend Node.js, Express
Mongoose (MongoDB ODM)
Socket.IO (real-time messaging)
JWT authentication
Helmet, express-rate-limit, express-mongo-sanitize
Services MongoDB Atlas
Cloudinary (media storage)
Resend (transactional email)
Railway (backend hosting)
Vercel (web hosting)
EAS Build (iOS builds)
All routes require Authorization: Bearer <token> except /api/auth/*.
| Method | Route | Description |
|---|---|---|
POST |
/api/auth/signup |
Create account |
POST |
/api/auth/login |
Login, returns JWT |
POST |
/api/auth/forgot-password |
Send reset email |
POST |
/api/auth/reset-password |
Reset with token |
GET |
/api/groups |
Discover public groups (filterable) |
GET |
/api/groups/my |
Groups current user has joined/created |
GET |
/api/groups/my-invites |
Pending group invitations |
GET |
/api/groups/:id |
Single group detail |
POST |
/api/groups |
Create a group |
PUT |
/api/groups/:id |
Edit group (admin only) |
DELETE |
/api/groups/:id |
Soft-delete group (creator only) |
POST |
/api/groups/:id/join |
Join or request to join |
POST |
/api/groups/:id/leave |
Leave a group |
POST |
/api/groups/:id/invite/:userId |
Invite a connection |
POST |
/api/groups/:id/rsvp-invite |
Accept/maybe/decline invite |
GET |
/api/groups/:id/members |
List group members |
GET |
/api/connections |
My accepted connections |
GET |
/api/connections/requests |
Pending requests received |
POST |
/api/connections/request/:userId |
Send connection request |
POST |
/api/connections/accept/:id |
Accept a request |
POST |
/api/connections/decline/:id |
Decline a request |
DELETE |
/api/connections/:id |
Remove a connection |
GET |
/api/connections/status/:userId |
Check connection status |
GET |
/api/meetups |
My meetups |
POST |
/api/meetups |
Create a meetup |
PUT |
/api/meetups/:id |
Edit meetup |
DELETE |
/api/meetups/:id |
Delete meetup |
POST |
/api/meetups/:id/rsvp |
RSVP to a meetup |
GET |
/api/users/profile/:userId |
Get any user's profile |
PUT |
/api/users/profile |
Update current user's profile |
GET |
/api/users/counts |
Badge counts (messages, invites, requests) |
GET |
/api/messages/conversations |
All direct message conversations |
GET |
/api/messages/:userId |
Messages with a specific user |
POST |
/api/messages |
Send a direct message |
GET |
/api/groups/:groupId/messages |
Group chat messages |
POST |
/api/groups/:groupId/messages |
Send group chat message |
name, email, password (bcrypt)
age, bio, profilePhoto, additionalPhotos
city, state, latitude, longitude
religion (String), politicalBeliefs (String)
lifeStage ([String]), familySituation ([String]), coreValues ([String], max 3)
causes ([String])
likes, passed, matches, blockedUsers (ObjectId refs)
pushTokens, emailNotifications
isVerified, onboardingComplete, isActive, isDeleted
name, description, category
city, state, isNationwide, isPrivate
coverPhoto, tags
createdBy, members, admins (User refs)
pendingRequests, invitedUsers, maybeUsers
isActive (soft delete), isSeeded, memberCount
from, to (User refs β unique pair)
status: 'pending' | 'accepted' | 'declined'
message (optional), sharedGroupId
title, description, dateTime
location: { address, city, state, zipCode }
creator, invitedUsers (User refs)
rsvps: [{ user, status: 'going' | 'maybe' | 'not-going' }]
maxAttendees, isActive
Auto-deploys from GitHub main. Set environment variables in Railway dashboard.
MONGODB_URI=
JWT_SECRET=
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=
RESEND_API_KEY=
CLIENT_URL=
NODE_ENV=production
PORT= # set automatically by RailwayAuto-deploys from GitHub main. Set REACT_APP_API_URL in Vercel dashboard.
npx eas build --platform ios --profile previewSubmit to TestFlight after build completes.
# Backend
cd backend
npm install
cp .env.example .env # fill in your env vars
npm run dev # runs on :5000
# Web
cd web
npm install
npm start # runs on :3000
# Mobile (phone on same WiFi)
cd mobile
npm install
npx expo start --lan
# Mobile (different network)
npx expo start --tunnel| Measure | Implementation |
|---|---|
| Authentication | JWT, 7-day expiry, verified on every protected route |
| Passwords | bcrypt, 12 salt rounds |
| Rate limiting | 500 req/15min general Β· 10 req/15min auth Β· 20 req/15min profile |
| NoSQL injection | express-mongo-sanitize strips $ and . from request bodies |
| HTTP hardening | Helmet β CSP, X-Frame-Options, HSTS, and more |
| CORS | Explicit allowlist β kindredpal.com, Vercel preview URLs, localhost |
| Soft deletes | Users and groups use isDeleted/isActive flags, never hard-deleted |
| Connection gating | Users must share an active group before sending a connection request |
KindredPal/
βββ backend/
β βββ middleware/
β β βββ auth.js # JWT verification
β βββ models/
β β βββ User.js
β β βββ Group.js
β β βββ Connection.js
β β βββ Meetup.js
β β βββ Message.js
β β βββ GroupMessage.js
β βββ routes/
β β βββ auth.js
β β βββ users.js
β β βββ groups.js
β β βββ connections.js
β β βββ meetups.js
β β βββ messages.js
β β βββ group-messages.js
β βββ services/
β β βββ notificationService.js
β βββ server.js
βββ mobile/
β βββ src/
β βββ screens/
β β βββ GroupsScreen.js
β β βββ GroupDetailScreen.js
β β βββ CreateGroupScreen.js
β β βββ ConnectionsScreen.js
β β βββ MemberProfileScreen.js
β β βββ MeetupsScreen.js
β β βββ MeetupDetailsScreen.js
β β βββ MessagesScreen.js
β β βββ ChatScreen.js
β β βββ ProfileScreen.js
β β βββ EditProfileScreen.js
β β βββ ...
β βββ services/
β β βββ api.js
β βββ context/
β βββ SocketContext.js
βββ web/
βββ src/
βββ pages/
βββ services/
βββ api.js