🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)
🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)

🔧 Programmierung 🕛 kürzlich 22 Min Lesezeit
0

From Zero to Storefront: My Journey Building a Property Rental Platform

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




Contents




  1. Introduction

  2. Tech Stack

  3. Quick Overview

  4. API

  5. Frontend

  6. Mobile App

  7. Admin Dashboard

  8. Points of Interest

  9. Resources



Source code:






Introduction



The idea emerged from a desire to build without boundaries – a fully customizable and operational property rental platform where every aspect is within your control:





  • Own the UI/UX: Design unique customer experiences without fighting against template limitations


  • Control the Backend: Implement custom business logic and data structures that perfectly match the requirements


  • Master DevOps: Deploy, scale, and monitor the application with preferred tools and workflows


  • Extend Freely: Add new features and integrations without platform constraints or additional fees






Tech Stack



Here's the tech stack that made it possible:




  • TypeScript

  • Node.js

  • MongoDB

  • React

  • MUI

  • Expo

  • Stripe

  • Docker



A key design decision was made to use TypeScript due to its numerous advantages. TypeScript offers strong typing, tooling, and integration, resulting in high-quality, scalable, more readable and maintainable code that is easy to debug and test.



I chose React for its powerful rendering capabilities, MongoDB for flexible data modeling, and Stripe for secure payment processing.



By choosing this stack, you're not just building a website and mobile app – you're investing in a foundation that can evolve with your needs, backed by robust open-source technologies and a growing developer community.






Quick overview



In this section, you'll see the main pages of the frontend, the admin dashboard and the mobile app.






Frontend



From the frontend, the customer can search for available properties, choose a property and checkout.



Below is the main page of the frontend where the customer can a location point and time, and search for available properties.





Below is the page where the customer can view the details of the property:





Below is the checkout page where the customer can set rental options and checkout. If the customer is not registered, he can checkout and register at the same time. He will receive a confirmation and activation email to set his password if he is not registered yet.





Below is the sign up page.





Below is the page where the customer can see a booking in detail.





Below is the page where the customer can manage his settings.





That's it. That's the main pages of the frontend.






Admin Dashboard



Three types of users:




  • Admins: They have full access to the admin dashboard. They can do everything.

  • Agencies: They have limited access on the admin dashboard. They can only manage their properties, bookings and customers.

  • Customers: They have access to the frontend and the mobile app only. They cannot access the admin dashboard.



The platform is designed to work with multiple agencies. Each agency can manage its properties, customers and bookings from the admin dashboard. The platform can also work with only one agency as well.



From the backend, admins can create and manage agencies, properties, locations, customers and bookings.



When new agencies are created, they receive an email prompting them to create their account to access the admin dashboard so they can manage their properties, customers and bookings.



Below is the sign in page of the admin dashboard.





If the status of a booking changes, the related customer will receive a notification and an email.



Below is the page where properties are displayed and can be managed.





Below is the page where admins and agencies can edit properties.





Below is the page where to create bookings if the agency wants to create a booking from the admin dashboard. Otherwise, bookings are created automatically when the checkout process is completed from the frontend or the mobile app.





Below is the page where to manage agencies.





Below is the page where to edit agencies.





Below is the page where to see customer's bookings.





There are other pages but these are the main pages of the admin dashboard.



That's it. That's the main pages of the admin dashboard.






API



and see all the routes.



Here is propertyRoutes.ts:




CODE
import express from 'express'
import multer from 'multer'
import routeNames from '../config/propertyRoutes.config'
import authJwt from '../middlewares/authJwt'
import * as propertyController from '../controllers/propertyController'

const routes = express.Router()

routes.route(routeNames.create).post(authJwt.verifyToken, propertyController.create)
routes.route(routeNames.update).put(authJwt.verifyToken, propertyController.update)
routes.route(routeNames.checkProperty).get(authJwt.verifyToken, propertyController.checkProperty)
routes.route(routeNames.delete).delete(authJwt.verifyToken, propertyController.deleteProperty)
routes.route(routeNames.uploadImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], propertyController.uploadImage)
routes.route(routeNames.deleteImage).post(authJwt.verifyToken, propertyController.deleteImage)
routes.route(routeNames.deleteTempImage).post(authJwt.verifyToken, propertyController.deleteTempImage)
routes.route(routeNames.getProperty).get(propertyController.getProperty)
routes.route(routeNames.getProperties).post(authJwt.verifyToken, propertyController.getProperties)
routes.route(routeNames.getBookingProperties).post(authJwt.verifyToken, propertyController.getBookingProperties)
routes.route(routeNames.getFrontendProperties).post(propertyController.getFrontendProperties)

export default routes






First of all, we create an Express Router. Then, we create the routes using their name, method, middlewares and controllers.



routeNames contains propertyRoutes route names:




CODE
const routes = {
create: '/api/create-property',
update: '/api/update-property',
delete: '/api/delete-property/:id',
uploadImage: '/api/upload-property-image',
deleteTempImage: '/api/delete-temp-property-image/:fileName',
deleteImage: '/api/delete-property-image/:property/:image',
getProperty: '/api/property/:id/:language',
getProperties: '/api/properties/:page/:size',
getBookingProperties: '/api/booking-properties/:page/:size',
getFrontendProperties: '/api/frontend-properties/:page/:size',
checkProperty: '/api/check-property/:id',
}

export default routes






propertyController contains the main business logic regarding locations. We are not going to see all the source code of the controller since it's quite large but we'll take create controller function for example.



Below is Property model:




CODE
import { Schema, model } from 'mongoose'
import * as movininTypes from ':movinin-types'
import * as env from '../config/env.config'

const propertySchema = new Schema<env.Property>(
{
name: {
type: String,
required: [true, "can't be blank"],
},
type: {
type: String,
enum: [
movininTypes.PropertyType.House,
movininTypes.PropertyType.Apartment,
movininTypes.PropertyType.Townhouse,
movininTypes.PropertyType.Plot,
movininTypes.PropertyType.Farm,
movininTypes.PropertyType.Commercial,
movininTypes.PropertyType.Industrial,
],
required: [true, "can't be blank"],
},
agency: {
type: Schema.Types.ObjectId,
required: [true, "can't be blank"],
ref: 'User',
index: true,
},
description: {
type: String,
required: [true, "can't be blank"],
},
available: {
type: Boolean,
default: true,
},
image: {
type: String,
},
images: {
type: [String],
},
bedrooms: {
type: Number,
required: [true, "can't be blank"],
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value',
},
},
bathrooms: {
type: Number,
required: [true, "can't be blank"],
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value',
},
},
kitchens: {
type: Number,
default: 1,
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value',
},
},
parkingSpaces: {
type: Number,
default: 0,
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value',
},
},
size: {
type: Number,
},
petsAllowed: {
type: Boolean,
required: [true, "can't be blank"],
},
furnished: {
type: Boolean,
required: [true, "can't be blank"],
},
minimumAge: {
type: Number,
required: [true, "can't be blank"],
min: env.MINIMUM_AGE,
max: 99,
},
location: {
type: Schema.Types.ObjectId,
ref: 'Location',
required: [true, "can't be blank"],
},
address: {
type: String,
},
price: {
type: Number,
required: [true, "can't be blank"],
},
hidden: {
type: Boolean,
default: false,
},
cancellation: {
type: Number,
default: 0,
},
aircon: {
type: Boolean,
default: false,
},
rentalTerm: {
type: String,
enum: [
movininTypes.RentalTerm.Monthly,
movininTypes.RentalTerm.Weekly,
movininTypes.RentalTerm.Daily,
movininTypes.RentalTerm.Yearly,
],
required: [true, "can't be blank"],
},
},
{
timestamps: true,
strict: true,
collection: 'Property',
},
)

const Property = model<env.Property>('Property', propertySchema)

export default Property






Below is Property type:




CODE
export interface Property extends Document {
name: string
type: movininTypes.PropertyType
agency: Types.ObjectId
description: string
image: string
images?: string[]
bedrooms: number
bathrooms: number
kitchens?: number
parkingSpaces?: number,
size?: number
petsAllowed: boolean
furnished: boolean
minimumAge: number
location: Types.ObjectId
address?: string
price: number
hidden?: boolean
cancellation?: number
aircon?: boolean
available?: boolean
rentalTerm: movininTypes.RentalTerm
}






A property is composed of:




  • A name

  • A type (Apartment, Commercial, Farm, House, Industrial, Plot, Townhouse)

  • A reference to the agency who created it

  • A description

  • A main image

  • Additional images

  • Number of bedrooms

  • Number of bathrooms

  • Number of kitchens

  • Number of parking spaces

  • A Size

  • Minimum age for rental

  • A location

  • An address (optional)

  • A price

  • A rental term (Monthly, Weekly, Daily, Yearly)

  • Cancellation price (set it to 0 to be included for free, leave it empty if you don't want to include it, or set the price for cancellation)

  • A flag that indicates whether pets are allowed or not

  • A flag that indicates whether the property is furnished or not

  • A flag that indicates whether the property is hidden or not

  • A flag that indicates whether aircon is available or not

  • A flag that indicates whether the property is available for rental or not



Below is create controller function:




CODE
export const create = async (req: Request, res: Response) => {
const { body }: { body: movininTypes.CreatePropertyPayload } = req

try {
const {
name,
type,
agency,
description,
image: imageFile,
images,
bedrooms,
bathrooms,
kitchens,
parkingSpaces,
size,
petsAllowed,
furnished,
minimumAge,
location,
address,
price,
hidden,
cancellation,
aircon,
rentalTerm,
} = body

const _property = {
name,
type,
agency,
description,
bedrooms,
bathrooms,
kitchens,
parkingSpaces,
size,
petsAllowed,
furnished,
minimumAge,
location,
address,
price,
hidden,
cancellation,
aircon,
rentalTerm,
}

const property = new Property(_property)
await property.save()

// image
const _image = path.join(env.CDN_TEMP_PROPERTIES, imageFile)
if (await helper.exists(_image)) {
const filename = `${property._id}_${Date.now()}${path.extname(imageFile)}`
const newPath = path.join(env.CDN_PROPERTIES, filename)

await fs.rename(_image, newPath)
property.image = filename
} else {
await Property.deleteOne({ _id: property._id })
const err = 'Image file not found'
logger.error(i18n.t('ERROR'), err)
return res.status(400).send(i18n.t('ERROR') + err)
}

// images
property.images = []
if (images) {
let i = 1
for (const img of images) {
const _img = path.join(env.CDN_TEMP_PROPERTIES, img)

if (await helper.exists(_img)) {
const filename = `${property._id}_${uuid()}_${Date.now()}_${i}${path.extname(img)}`
const newPath = path.join(env.CDN_PROPERTIES, filename)

await fs.rename(_img, newPath)
property.images.push(filename)
} else {
await Property.deleteOne({ _id: property._id })
const err = 'Image file not found'
logger.error(i18n.t('ERROR'), err)
return res.status(400).send(i18n.t('ERROR') + err)
}
i += 1
}
}

await property.save()

return res.json(property)
} catch (err) {
logger.error(`[property.create] ${i18n.t('DB_ERROR')} ${JSON.stringify(body)}`, err)
return res.status(400).send(i18n.t('ERROR') + err)
}
}









Frontend



The frontend is a web application built with Node.js, React, MUI and TypeScript. From the frontend, the customer can search for available cars depending on pickup and drop-off points and time, choose a car and proceed to checkout:




  • ./frontend/src/assets/ folder contains CSS and images.

  • ./frontend/src/pages/ folder contains React pages.

  • ./frontend/src/components/ folder contains React components.

  • ./frontend/src/services/ contains api client services.

  • ./frontend/src/App.tsx is the main React App that contains routes.

  • ./frontend/src/index.tsx is the main entry point of the frontend.



TypeScript type definitions are defined in the package ./packages/movinin-types.



App.tsx is the main react App:




CODE
import React, { lazy, Suspense } from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import env from '@/config/env.config'
import { GlobalProvider } from '@/context/GlobalContext'
import { init as initGA } from '@/common/ga4'

if (env.GOOGLE_ANALYTICS_ENABLED) {
initGA()
}

const SignIn = lazy(() => import('@/pages/SignIn'))
const SignUp = lazy(() => import('@/pages/SignUp'))
const Activate = lazy(() => import('@/pages/Activate'))
const ForgotPassword = lazy(() => import('@/pages/ForgotPassword'))
const ResetPassword = lazy(() => import('@/pages/ResetPassword'))
const Home = lazy(() => import('@/pages/Home'))
const Search = lazy(() => import('@/pages/Search'))
const Property = lazy(() => import('@/pages/Property'))
const Checkout = lazy(() => import('@/pages/Checkout'))
const CheckoutSession = lazy(() => import('@/pages/CheckoutSession'))
const Bookings = lazy(() => import('@/pages/Bookings'))
const Booking = lazy(() => import('@/pages/Booking'))
const Settings = lazy(() => import('@/pages/Settings'))
const Notifications = lazy(() => import('@/pages/Notifications'))
const ToS = lazy(() => import('@/pages/ToS'))
const About = lazy(() => import('@/pages/About'))
const ChangePassword = lazy(() => import('@/pages/ChangePassword'))
const Contact = lazy(() => import('@/pages/Contact'))
const NoMatch = lazy(() => import('@/pages/NoMatch'))
const Agencies = lazy(() => import('@/pages/Agencies'))
const Locations = lazy(() => import('@/pages/Locations'))

const App = () => (
<GlobalProvider>
<Router>
<div className="app">
<Suspense fallback={<></>}>
<Routes>
<Route path="/sign-in" element={<SignIn />} />
<Route path="/sign-up" element={<SignUp />} />
<Route path="/activate" element={<Activate />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/" element={<Home />} />
<Route path="/search" element={<Search />} />
<Route path="/property" element={<Property />} />
<Route path="/checkout" element={<Checkout />} />
<Route path="/checkout-session/:sessionId" element={<CheckoutSession />} />
<Route path="/bookings" element={<Bookings />} />
<Route path="/booking" element={<Booking />} />
<Route path="/settings" element={<Settings />} />
<Route path="/notifications" element={<Notifications />} />
<Route path="/change-password" element={<ChangePassword />} />
<Route path="/about" element={<About />} />
<Route path="/tos" element={<ToS />} />
<Route path="/contact" element={<Contact />} />
<Route path="/agencies" element={<Agencies />} />
<Route path="/destinations" element={<Locations />} />

<Route path="*" element={<NoMatch />} />
</Routes>
</Suspense>
</div>
</Router>
</GlobalProvider>
)

export default App






We are using React lazy loading to load each route.



We are not going to cover each page of the frontend, but you can browse the and see each one.






Admin Dashboard



The admin dashboard is a web application built with Node.js, React, MUI and TypeScript. From the backend, admins can create and manage suppliers, cars, locations, customers and bookings. When new suppliers are created from the backend, they will receive an email prompting them to create an account in order to access the admin dashboard and manage their car fleet and bookings.




  • ./backend/assets/ folder contains CSS and images.

  • ./backend/pages/ folder contains React pages.

  • ./backend/components/ folder contains React components.

  • ./backend/services/ contains api client services.

  • ./backend/App.tsx is the main React App that contains routes.

  • ./backend/index.tsx is the main entry point of the admin dashboard.



TypeScript type definitions are defined in the package ./packages/movinin-types.



App.tsx of the admin dashboard follow similar logic like App.tsx of the frontend.



We are not going to cover each page of the admin dashboard but you can browse the































  • Vollständiger Original-Bericht
    Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
    ↗ Original-Artikel auf dev.to lesen
    Wie bewertest du diesen Beitrag?
    1 Klick Feedback
    Teilen mit Netzwerk & Team:

    Community-Analysen & Experten-Meinungen 0

    Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
    Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
    Community Pulse: Relevanz-Einschätzung
    1 Klick Experten-Votum
    🔴 Akute Relevanz 0%
    🟡 In Evaluierung 0%
    🟢 Keine Auswirkung 0%
    Spannende Innovation 0%
    Verwandte Story-Cluster & Quellen (Vektor-KI)
    Port 8095 Engine
    3 Quellen
    GPT-6 Astra Release Today? OpenAI’s Next Major AI Model Is Almost Here
    1 Quelle
    Apple accuses OpenAI of destroying evidence as trade-secrets fight intensifies
    1 Quelle
    Major AI platforms go down in unprecedented simultaneous outage
    Ähnliche Beiträge
    🔍 Verwandte News

    Auch interessante Nachrichten From Zero to Storefront: My Journey Building a Property Rental Platform

    Thematisch verwandte Begriffe: From, Zero, Storefront, Journey · 6 Treffer

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...