Neighbour Needs is a full stack MERN application (MongoDB, Express, React and Node) that allows users to find people in their neighbourhood who can help with anything from maths tutoring and interior design, to plumbing and therapy. This project was created by Ana Borges, Emily Daykin and Mohamed Mohamed in the span of just over a week. For a full list of this app's features, see the Features section below.
This repo contains code for the back end server only; code for the front end client lives here.
- Check out the live application here!
- Feel free to register and then use your own login credentials, or try a demo one using:
- Username:
kc@user.com
- Password:
Password1!@
- Username:
- Feel free to register and then use your own login credentials, or try a demo one using:
- Or run it locally (make sure you have a local version of MongoDB running):
- Front End: Clone this repo, → run
npm install
→ runnpm run start:client
- Back End: Clone this repo → run
npm install
→ runnpm run seed
→ runnpm run start:server
- Front End: Clone this repo, → run
- React Framework (Single Page Application)
- API Handling: Axios
- Pure CSS with Sass
- React-Router-Dom
- Server: Node.js & Express
- Database: MongoDB & Mongoose
- Safeguarding from injection attacks: Express Mongo Sanitize
- Password Encryption: Bcrypt
- Authentication: JSON Web Token (JWT)
- Git, GitHub
- Trello for project management
- Postman for API testing
- Excalidraw for wireframing
- Npm
- Deployment:
- Front End: Netlify
- Back End:
HerokuRender (& Mongo Atlas)
- Display of all profiles, and routing to an individual profile page with more information and a comments area when clicked on
- Real time searching through all profiles by name, location, or service offered
- Minimalist top navbar with a more detailed slide-in-out sidebar
- Log In and Register functionality
- Once logged in:
- A user icon appears in the navbar, as well as a personalised welcome banner, which redirects to the user's profile page
- The user can create a post
- The user can leave a comment on any profile
- Only the same user who commented/posted can remove their comment and post, no one else's
- Filtering through service type or location via their respective pages
- Front End:
- React Components to compartmentalise code
- React Hooks for state management and handling side effects
- Scss stylesheets per react component
- Single Page Application (
react-router-dom
) usingLink
,useNavigate
,useLocation
anduseParams
- Back End:
- All security checks (user access credentials) done in the back end:
- Email validation (correct format and uniqueness)
- Password validation (encryption and strength: minimum of 8 characters, at least one lowercase & uppercase letter and number)
- Obscuring the password response from the front end
- Login credentials expire after 6 hours
- Secure routing middelware to verify logged in users, same users (only that same user can delete their comment for example) and admin users
- Error handling middleware to assist with debugging
- 3 interlinked schema models in MongoDB for profiles, comments and posts
- Data seeding of 25 user profiles, 15 comments and 3 posts.
- All security checks (user access credentials) done in the back end:
New post pop-up (only when authenticated) using css position: absolute
. User can also delete their posts only`.
const [createPostPopup, setCreatePostPopup] = useState(false);
const [newPostData, setNewPostData] = useState({
text: '',
service: '',
urgency: ''
});
const createPostClicked = () => setCreatePostPopup(!createPostPopup);
function handlePostInputChange(e) {
setNewPostData({ ...newPostData, [e.target.name]: e.target.value });
}
async function handleSubmitPost(e) {
e.preventDefault();
await createPost(newPostData);
setNewPostData({ text: '', service: '', urgency: '' });
setCreatePostPopup(!createPostPopup);
getPostData();
}
async function handleDeletePost(postId) {
await deletePost(postId);
getPostData();
}
import jwt from 'jsonwebtoken';
import Profile from '../models/profile.js';
import { secret } from '../config/environment.js';
const secureRoute = async (req, res, next) => {
try {
const authToken = req.headers.authorization;
if (!authToken || !authToken.startsWith('Bearer')) {
return res
.status(401)
.send({ message: 'Unauthorised. Auth Token incorrect or does not exist' });
} else {
const token = authToken.replace('Bearer ', '');
jwt.verify(token, secret, async (err, data) => {
if (err) {
return res.status(400).json({ message: "Unauthorised. JWT can't verify." });
} else {
const user = await Profile.findById(data.profileId);
if (!user) {
return res.status(401).json({ message: 'Unauthorised. User not in database' });
} else {
req.currentUser = user;
next();
}
}
});
}
} catch (err) {
return res.status(401).send({ message: 'Unauthorised' });
}
};
export default secureRoute;
export const commentSchema = new mongoose.Schema(
{
text: { type: String, required: true, maxLength: 300 },
rating: { type: Number, required: true, min: 1, max: 5 },
createdById: {
type: mongoose.Schema.ObjectId,
ref: 'Profile',
required: true
},
createdByName: {
type: String
},
createdBySurname: {
type: String
}
},
{ timestamps: true }
);
const profileSchema = new mongoose.Schema({
firstName: { type: String, required: [true, 'First name required'] },
surname: { type: String, required: [true, 'Surname required'] },
email: {
type: String,
required: [true, 'Email required'],
unique: true,
validate: (email) => emailRegex.test(email)
},
password: {
type: String,
required: [true, 'Password required'],
minlength: [8, 'Password must be a minimum of 8 characters'],
validate: (password) => passwordRegex.test(password)
},
isHelper: { type: Boolean },
averageRating: { type: String },
services: { type: Array },
bio: { type: String },
city: { type: String, required: [true, 'City required'] },
region: { type: String, required: [true, 'Region required'] },
imageProfile: { type: String },
imageService: { type: String },
comments: [commentSchema],
posts: { type: Array },
isAdmin: { type: Boolean }
});
If we'd had more time as a group, we would've loved to implement an edit profile function (where a user can edit their own profile, become a helper, add a bio etc), as well as messaging functionality where users can reach out to helpers to arrange appointments and request more information. One unsolved problem we had was registered a new user a helper: when a new user registers and fills out the services they can help out with, they don't get saved to the database as a helper.