Skip to content

vijethmd/StayIn-Rental

Repository files navigation

StayIn — Full-Stack Rental Platform

StayIn is a full-stack rental web platform where users can publish, browse, review and map property listings. It is built with Node.js, Express.js and MongoDB following the MVC architecture for scalability and modularity.

✨ Features

  • Listings CRUD — create, read, update and delete property listings.
  • Authentication & sessions — secure sign-up / log-in / log-out with Passport.js (passport-local-mongoose), Express-Session and a MongoDB-backed session store.
  • Authorization — only the owner of a listing can edit/delete it; only a review's author can delete it.
  • Image uploads — listing photos uploaded to Cloudinary via Multer.
  • Interactive maps — each listing's location is geocoded and shown on a Mapbox map.
  • Reviews & ratings — star ratings and comments on listings.
  • Server-side validation — request bodies validated with Joi; flash messages for success/error feedback.
  • Server-side rendering — dynamic pages with EJS + ejs-mate layouts.

🧰 Tech Stack

Layer Technology
Runtime Node.js
Framework Express.js
Database MongoDB + Mongoose
Auth Passport.js, passport-local-mongoose, express-session, connect-mongo
File uploads Multer + multer-storage-cloudinary + Cloudinary
Maps Mapbox (mapbox-gl + @mapbox/mapbox-sdk)
Views EJS + ejs-mate
Validation Joi
Misc method-override, connect-flash, dotenv

📁 Project Structure

stayin-rental/
├── app.js                 # App entry point, middleware & route mounting
├── cloudConfig.js         # Cloudinary + Multer storage configuration
├── middleware.js          # isLoggedIn, isOwner, validation, etc.
├── schema.js              # Joi validation schemas
├── models/                # Mongoose models (listing, review, user)
├── controllers/           # Route handlers (listings, reviews, users)
├── routes/                # Express routers (listing, review, user)
├── views/                 # EJS templates (listings, users, includes, layouts)
├── public/                # Static assets (css, client-side js incl. map.js)
├── utils/                 # ExError (custom error), wrapAsync helper
├── init/                  # Database seeding (data.js + index.js)
└── .env                   # Secrets (NOT committed — see .env.example)

🚀 Getting Started

1. Prerequisites

  • Node.js (v18+ recommended)
  • A MongoDB database — local (mongod) or a free MongoDB Atlas cluster
  • A Cloudinary account (free tier)
  • A Mapbox account (free tier)

2. Install

git clone <your-repo-url> stayin
cd stayin
npm install

3. Configure environment

cp .env.example .env

Then fill in .env (see Setup guides below for where each value comes from):

ATLAS_DB=<your mongodb connection string>
SECRET=<any long random string>
CLOUD_NAME=<cloudinary cloud name>
CLOUD_API_KEY=<cloudinary api key>
CLOUD_API_SECRET=<cloudinary api secret>
MAP_TOKEN=<mapbox public token>

4. Seed sample listings (optional)

npm run seed

5. Run

npm run dev     # with nodemon (auto-reload) — run: npm i -g nodemon
# or
npm start       # plain node

Visit http://localhost:8080/listings


🔧 Setup guides: Multer, Cloudinary & Mapbox

A. Cloudinary + Multer (image uploads)

1. Create a Cloudinary account at https://cloudinary.com and open the Dashboard. Copy your Cloud name, API Key and API Secret into .env:

CLOUD_NAME=...
CLOUD_API_KEY=...
CLOUD_API_SECRET=...

2. Install the packages (already in package.json):

npm install multer cloudinary multer-storage-cloudinary

3. Configure Cloudinary storagecloudConfig.js:

const cloudinary = require('cloudinary').v2;
const { CloudinaryStorage } = require('multer-storage-cloudinary');

cloudinary.config({
  cloud_name: process.env.CLOUD_NAME,
  api_key: process.env.CLOUD_API_KEY,
  api_secret: process.env.CLOUD_API_SECRET,
});

const storage = new CloudinaryStorage({
  cloudinary,
  params: {
    folder: 'StayIn_DEV',                 // Cloudinary folder name
    allowedFormats: ['png', 'jpg', 'jpeg'],
  },
});

module.exports = { cloudinary, storage };

4. Wire Multer into the route — in routes/listing.js:

const multer = require('multer');
const { storage } = require('../cloudConfig.js');
const upload = multer({ storage });

// the form field name must be "listing[image]"
router.post('/', upload.single('listing[image]'), wrapAsync(listingController.createListing));

5. In the controller (controllers/listings.js) the uploaded file is on req.file:

const url = req.file.path;        // Cloudinary URL
const filename = req.file.filename;
newListing.image = { url, filename };

6. In the form (views/listings/new.ejs) the form must be multipart/form-data:

<form action="/listings" method="POST" enctype="multipart/form-data">
  <input name="listing[image]" type="file" />
</form>

B. Mapbox (interactive maps)

1. Create a Mapbox account at https://account.mapbox.com. Under Tokens, copy your Default public token (starts with pk.) into .env:

MAP_TOKEN=pk....

2. Install the server SDK (already in package.json):

npm install @mapbox/mapbox-sdk

3. Geocode on the server when a listing is created — controllers/listings.js:

const mbxGeocoding = require('@mapbox/mapbox-sdk/services/geocoding');
const geocodingClient = mbxGeocoding({ accessToken: process.env.MAP_TOKEN });

const response = await geocodingClient
  .forwardGeocode({ query: req.body.listing.location, limit: 1 })
  .send();

// store GeoJSON Point on the listing
newListing.geometry = response.body.features[0].geometry;

The Listing model stores this as a GeoJSON field:

geometry: {
  type: { type: String, enum: ['Point'], required: true },
  coordinates: { type: [Number], required: true },
}

4. Load Mapbox GL on the pageviews/layouts/boilerplate.ejs already includes:

<link href="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js"></script>

5. Pass the token + coordinates to the client — in views/listings/show.ejs:

<script>
  const mapToken = "<%= process.env.MAP_TOKEN %>";
  const listing = <%- JSON.stringify(listing) %>;
</script>
<script src="/js/map.js"></script>

6. Render the mappublic/js/map.js:

mapboxgl.accessToken = mapToken;
const map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/streets-v12',
  center: listing.geometry.coordinates,
  zoom: 9,
});
new mapboxgl.Marker({ color: 'red' })
  .setLngLat(listing.geometry.coordinates)
  .addTo(map);

📜 Available scripts

Command Description
npm start Start the server (node app.js)
npm run dev Start with nodemon auto-reload
npm run seed Seed the database with sample listings

⚠️ Security note

Never commit your real .env. It holds Cloudinary, Mapbox and database secrets. It is already listed in .gitignore.

📄 License

MIT

About

StayIn — full-stack rental platform (Node.js, Express, MongoDB, Passport.js, Cloudinary, Multer, Mapbox, EJS) built with the MVC architecture.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors