


Von Null bis Storefront: Meine Reise zum Aufbau einer Immobilienvermietungsplattform
Inhalt
- Einführung
- Tech Stack
- Kurzübersicht
- API
- Frontend
- Mobile App
- Admin-Dashboard
- Sehenswürdigkeiten
- Ressourcen
Quellcode: https://github.com/aelassas/movinin
Demo: https://movinin.dynv6.net:3004
Einführung
Die Idee entstand aus dem Wunsch heraus, ohne Grenzen zu bauen – eine vollständig anpassbare und betriebsfähige Immobilienvermietungsplattform, bei der jeder Aspekt in Ihrer Kontrolle liegt:
- Besitzen Sie die Benutzeroberfläche/UX: Entwerfen Sie einzigartige Kundenerlebnisse, ohne gegen Vorlagenbeschränkungen anzukämpfen
- Kontrollieren Sie das Backend: Implementieren Sie benutzerdefinierte Geschäftslogik und Datenstrukturen, die perfekt zu den Anforderungen passen
- Master DevOps: Stellen Sie die Anwendung mit bevorzugten Tools und Workflows bereit, skalieren und überwachen Sie sie
- Frei erweitern: Fügen Sie neue Funktionen und Integrationen ohne Plattformbeschränkungen oder zusätzliche Gebühren hinzu
Tech-Stack
Hier ist der Tech-Stack, der es möglich gemacht hat:
- TypeScript
- Node.js
- MongoDB
- Reagieren
- MUI
- Ausstellung
- Streifen
- Docker
Eine wichtige Designentscheidung wurde aufgrund seiner zahlreichen Vorteile für die Verwendung von TypeScript getroffen. TypeScript bietet starke Typisierung, Tools und Integration, was zu hochwertigem, skalierbarem, besser lesbarem und wartbarem Code führt, der einfach zu debuggen und zu testen ist.
Ich habe mich für React wegen seiner leistungsstarken Rendering-Funktionen, MongoDB für die flexible Datenmodellierung und Stripe für die sichere Zahlungsabwicklung entschieden.
Wenn Sie sich für diesen Stack entscheiden, erstellen Sie nicht nur eine Website und eine mobile App – Sie investieren in eine Grundlage, die sich mit Ihren Anforderungen weiterentwickeln kann, unterstützt durch robuste Open-Source-Technologien und eine wachsende Entwickler-Community.
Schneller Überblick
In diesem Abschnitt sehen Sie die Hauptseiten des Frontends, des Admin-Dashboards und der mobilen App.
Frontend
Vom Frontend aus kann der Kunde nach verfügbaren Immobilien suchen, eine Immobilie auswählen und zur Kasse gehen.
Unten befindet sich die Hauptseite des Frontends, auf der der Kunde einen Ort und eine Uhrzeit eingeben und nach verfügbaren Immobilien suchen kann.
Unten finden Sie das Suchergebnis der Hauptseite, auf der der Kunde eine Immobilie zur Vermietung auswählen kann.
Unten finden Sie die Seite, auf der der Kunde die Details der Immobilie einsehen kann:
Unten finden Sie eine Ansicht der Bilder der Immobilie:
Unten befindet sich die Checkout-Seite, auf der der Kunde Mietoptionen und den Checkout festlegen kann. Ist der Kunde nicht registriert, kann er zur Kasse gehen und sich gleichzeitig registrieren. Er erhält eine Bestätigungs- und Aktivierungs-E-Mail, um sein Passwort festzulegen, wenn er noch nicht registriert ist.
Unten finden Sie die Anmeldeseite. In der Produktion sind Authentifizierungscookies httpOnly, signiert, sicher und strikt sameSite. Diese Optionen verhindern XSS-, CSRF- und MITM-Angriffe. Authentifizierungscookies sind auch über eine benutzerdefinierte Middleware vor XST-Angriffen geschützt.
Unten finden Sie die Anmeldeseite.
Unten finden Sie die Seite, auf der der Kunde seine Buchungen einsehen und verwalten kann.
Unten befindet sich die Seite, auf der der Kunde eine Buchung im Detail sehen kann.
Unten finden Sie die Seite, auf der der Kunde seine Benachrichtigungen sehen kann.
Unten finden Sie die Seite, auf der der Kunde seine Einstellungen verwalten kann.
Unten finden Sie die Seite, auf der der Kunde sein Passwort ändern kann.
Das ist es. Das sind die Hauptseiten des Frontends.
Admin-Dashboard
Drei Arten von Benutzern:
- Administratoren: Sie haben vollen Zugriff auf das Admin-Dashboard. Sie können alles.
- Agenturen: Sie haben eingeschränkten Zugriff auf das Admin-Dashboard. Sie können nur ihre Objekte, Buchungen und Kunden verwalten.
- Kunden: Sie haben nur Zugriff auf das Frontend und die mobile App. Sie können nicht auf das Admin-Dashboard zugreifen.
Die Plattform ist für die Zusammenarbeit mit mehreren Agenturen konzipiert. Jede Agentur kann ihre Unterkünfte, Kunden und Buchungen über das Admin-Dashboard verwalten. Die Plattform kann auch mit nur einer Agentur zusammenarbeiten.
Vom Backend aus können Administratoren Agenturen, Objekte, Standorte, Kunden und Buchungen erstellen und verwalten.
Wenn neue Agenturen erstellt werden, erhalten sie eine E-Mail mit der Aufforderung, ihr Konto zu erstellen, um auf das Admin-Dashboard zuzugreifen, damit sie ihre Unterkünfte, Kunden und Buchungen verwalten können.
Unten finden Sie die Anmeldeseite des Admin-Dashboards.
Unten finden Sie die Dashboard-Seite, auf der Administratoren und Agenturen Buchungen sehen und verwalten können.
Wenn sich der Status einer Buchung ändert, erhält der entsprechende Kunde eine Benachrichtigung und eine E-Mail.
Unten finden Sie die Seite, auf der Eigenschaften angezeigt und verwaltet werden können.
Unten finden Sie die Seite, auf der Administratoren und Agenturen neue Immobilien erstellen können, indem sie Bilder und Immobilieninformationen bereitstellen. Für eine kostenlose Stornierung setzen Sie den Wert auf 0. Andernfalls legen Sie den Preis der Option fest oder lassen Sie das Feld leer, wenn Sie sie nicht einbeziehen möchten.
Unten finden Sie die Seite, auf der Administratoren und Agenturen Eigenschaften bearbeiten können.
Unten finden Sie die Seite, auf der Administratoren Kunden verwalten können.
Unten finden Sie die Seite, auf der Sie Buchungen erstellen können, wenn die Agentur eine Buchung über das Admin-Dashboard erstellen möchte. Andernfalls werden Buchungen automatisch erstellt, wenn der Checkout-Vorgang über das Frontend oder die mobile App abgeschlossen wird.
Unten finden Sie die Seite, auf der Sie Buchungen bearbeiten können.
Unten finden Sie die Seite, auf der Sie Agenturen verwalten können.
Unten finden Sie die Seite, auf der Sie neue Agenturen erstellen können.
Unten finden Sie die Seite, auf der Sie Agenturen bearbeiten können.
Unten finden Sie die Seite, auf der Sie die Immobilien der Agenturen sehen können.
Unten finden Sie die Seite, auf der Sie die Buchungen der Kunden sehen können.
Unten finden Sie die Seite, auf der Administratoren und Agenturen ihre Einstellungen verwalten können.
Es gibt noch andere Seiten, aber dies sind die Hauptseiten des Admin-Dashboards.
Das ist es. Das sind die Hauptseiten des Admin-Dashboards.
API
Die API stellt alle Funktionen bereit, die für das Admin-Dashboard, das Frontend und die mobile App benötigt werden. Die API folgt dem MVC-Entwurfsmuster. Zur Authentifizierung wird JWT verwendet. Es gibt einige Funktionen, die eine Authentifizierung erfordern, wie zum Beispiel Funktionen im Zusammenhang mit der Verwaltung von Unterkünften, Buchungen und Kunden, und andere, die keine Authentifizierung erfordern, wie zum Beispiel das Abrufen von Standorten und verfügbaren Unterkünften für nicht authentifizierte Benutzer:
- ./api/src/models/ Ordner enthält MongoDB-Modelle.
- ./api/src/routes/-Ordner enthält Express-Routen.
- ./api/src/controllers/ Ordner enthält Controller.
- ./api/src/middlewares/ Ordner enthält Middlewares.
- ./api/src/config/env.config.ts enthält die Konfiguration und TypeScript-Typdefinitionen.
- ./api/src/lang/-Ordner enthält die Lokalisierung.
- ./api/src/app.ts ist der Hauptserver, auf den Routen geladen werden.
- ./api/index.ts ist der Haupteinstiegspunkt der API.
index.ts ist der Haupteinstiegspunkt der API:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
Dies ist eine TypeScript-Datei, die einen Server mit Node.js und Express startet. Es importiert mehrere Module, darunter dotenv, Process, fs, http, https, mongoose und app. Anschließend prüft es, ob die HTTPS-Umgebungsvariable auf „true“ gesetzt ist, und erstellt in diesem Fall mithilfe des https-Moduls und des bereitgestellten privaten Schlüssels und Zertifikats einen HTTPS-Server. Andernfalls wird mithilfe des http-Moduls ein HTTP-Server erstellt. Der Server lauscht auf dem Port, der in der Umgebungsvariablen PORT angegeben ist.
Die Schließfunktion ist so definiert, dass sie den Server ordnungsgemäß stoppt, wenn ein Beendigungssignal empfangen wird. Es schließt den Server und die MongoDB-Verbindung und beendet dann den Prozess mit dem Statuscode 0. Schließlich registriert es die Schließfunktion, die aufgerufen werden soll, wenn der Prozess ein SIGINT-, SIGTERM- oder SIGQUIT-Signal empfängt.
app.ts ist der Haupteinstiegspunkt der API:
import express from 'express' import compression from 'compression' import helmet from 'helmet' import nocache from 'nocache' import cookieParser from 'cookie-parser' import i18n from './lang/i18n' import * as env from './config/env.config' import cors from './middlewares/cors' import allowedMethods from './middlewares/allowedMethods' import agencyRoutes from './routes/agencyRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import propertyRoutes from './routes/propertyRoutes' import userRoutes from './routes/userRoutes' import stripeRoutes from './routes/stripeRoutes' import countryRoutes from './routes/countryRoutes' import * as helper from './common/helper' const app = express() app.use(helmet.contentSecurityPolicy()) app.use(helmet.dnsPrefetchControl()) app.use(helmet.crossOriginEmbedderPolicy()) app.use(helmet.frameguard()) app.use(helmet.hidePoweredBy()) app.use(helmet.hsts()) app.use(helmet.ieNoOpen()) app.use(helmet.noSniff()) app.use(helmet.permittedCrossDomainPolicies()) app.use(helmet.referrerPolicy()) app.use(helmet.xssFilter()) app.use(helmet.originAgentCluster()) app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' })) app.use(helmet.crossOriginOpenerPolicy()) app.use(nocache()) app.use(compression({ threshold: 0 })) app.use(express.urlencoded({ limit: '50mb', extended: true })) app.use(express.json({ limit: '50mb' })) app.use(cors()) app.options('*', cors()) app.use(cookieParser(env.COOKIE_SECRET)) app.use(allowedMethods) app.use('/', agencyRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', propertyRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE helper.mkdir(env.CDN_USERS) helper.mkdir(env.CDN_TEMP_USERS) helper.mkdir(env.CDN_PROPERTIES) helper.mkdir(env.CDN_TEMP_PROPERTIES) helper.mkdir(env.CDN_LOCATIONS) helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
Zuerst rufen wir die MongoDB-Verbindungszeichenfolge ab und stellen dann eine Verbindung mit der MongoDB-Datenbank her. Dann erstellen wir eine Express-App und laden Middleware wie Cors, Kompression, Helm und Nocache. Mithilfe der Helm-Middleware-Bibliothek haben wir verschiedene Sicherheitsmaßnahmen eingerichtet. Wir importieren auch verschiedene Routendateien für verschiedene Teile der Anwendung, z. B. „supplierRoutes“, „bookingRoutes“, „locationRoutes“, „notificationRoutes“, „propertyRoutes“ und „userRoutes“. Zum Schluss laden wir Express-Routen und exportieren die App.
Es gibt 8 Routen in der API. Jede Route verfügt über einen eigenen Controller, der dem MVC-Entwurfsmuster und den SOLID-Prinzipien folgt. Nachfolgend sind die Hauptrouten aufgeführt:
- userRoutes: Stellt REST-Funktionen für Benutzer bereit
- agencyRoutes: Bietet REST-Funktionen im Zusammenhang mit Agenturen
- countryRoutes: Bietet REST-Funktionen im Zusammenhang mit Ländern
- locationRoutes: Bietet REST-Funktionen im Zusammenhang mit Standorten
- propertyRoutes: Stellt REST-Funktionen im Zusammenhang mit Eigenschaften bereit
- bookingRoutes: Bietet REST-Funktionen im Zusammenhang mit Buchungen
- notificationRoutes: Bietet REST-Funktionen im Zusammenhang mit Benachrichtigungen
- stripeRoutes: Bietet REST-Funktionen im Zusammenhang mit dem Stripe-Zahlungsgateway
Wir werden nicht jede Route einzeln erklären. Wir nehmen zum Beispiel propertyRoutes und sehen, wie es erstellt wurde. Sie können den Quellcode durchsuchen und alle Routen sehen.
Hier ist propertyRoutes.ts:
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
Zuerst erstellen wir einen Express-Router. Anschließend erstellen wir die Routen anhand ihres Namens, ihrer Methode, Middleware und Controller.
routeNames enthält propertyRoutes-Routennamen:
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 enthält die Hauptgeschäftslogik in Bezug auf Standorte. Wir werden nicht den gesamten Quellcode des Controllers sehen, da er recht umfangreich ist, aber wir nehmen zum Beispiel die Funktion zum Erstellen eines Controllers.
Unten finden Sie das Immobilienmodell:
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 </env.property></env.property>
Unten finden Sie den Immobilientyp:
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 }
Eine Eigenschaft besteht aus:
- Ein Name
- Typ A (Wohnung, Gewerbe, Bauernhof, Haus, Industrie, Grundstück, Reihenhaus)
- Ein Verweis auf die Agentur, die es erstellt hat
- Eine Beschreibung
- Ein Hauptbild
- Zusätzliche Bilder
- Anzahl der Schlafzimmer
- Anzahl der Badezimmer
- Anzahl der Küchen
- Anzahl der Parkplätze
- Größe A
- Mindestalter für die Anmietung
- Ein Ort
- Eine Adresse (optional)
- Ein Preis
- Eine Mietdauer (monatlich, wöchentlich, täglich, jährlich)
- Stornierungspreis (setzen Sie ihn auf 0, um ihn kostenlos einzuschließen, lassen Sie das Feld leer, wenn Sie ihn nicht einschließen möchten, oder legen Sie den Preis für die Stornierung fest)
- Eine Flagge, die angibt, ob Haustiere erlaubt sind oder nicht
- Eine Markierung, die angibt, ob die Immobilie möbliert ist oder nicht
- Eine Markierung, die angibt, ob die Eigenschaft ausgeblendet ist oder nicht
- Eine Flagge, die anzeigt, ob eine Klimaanlage verfügbar ist oder nicht
- Eine Markierung, die angibt, ob die Immobilie zur Vermietung verfügbar ist oder nicht
Unten finden Sie die Funktion zum Erstellen eines Controllers:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
Frontend
Das Frontend ist eine Webanwendung, die mit Node.js, React, MUI und TypeScript erstellt wurde. Im Frontend kann der Kunde je nach Abhol- und Rückgabeort und -zeit nach verfügbaren Autos suchen, ein Auto auswählen und zur Kasse gehen:
- ./frontend/src/assets/ Ordner enthält CSS und Bilder.
- ./frontend/src/pages/ Ordner enthält React-Seiten.
- ./frontend/src/components/ Ordner enthält React-Komponenten.
- ./frontend/src/services/ enthält API-Client-Dienste.
- ./frontend/src/App.tsx ist die Haupt-React-App, die Routen enthält.
- ./frontend/src/index.tsx ist der Haupteinstiegspunkt des Frontends.
TypeScript-Typdefinitionen werden im Paket ./packages/movinin-types definiert.
App.tsx ist die Hauptreaktions-App:
import express from 'express' import compression from 'compression' import helmet from 'helmet' import nocache from 'nocache' import cookieParser from 'cookie-parser' import i18n from './lang/i18n' import * as env from './config/env.config' import cors from './middlewares/cors' import allowedMethods from './middlewares/allowedMethods' import agencyRoutes from './routes/agencyRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import propertyRoutes from './routes/propertyRoutes' import userRoutes from './routes/userRoutes' import stripeRoutes from './routes/stripeRoutes' import countryRoutes from './routes/countryRoutes' import * as helper from './common/helper' const app = express() app.use(helmet.contentSecurityPolicy()) app.use(helmet.dnsPrefetchControl()) app.use(helmet.crossOriginEmbedderPolicy()) app.use(helmet.frameguard()) app.use(helmet.hidePoweredBy()) app.use(helmet.hsts()) app.use(helmet.ieNoOpen()) app.use(helmet.noSniff()) app.use(helmet.permittedCrossDomainPolicies()) app.use(helmet.referrerPolicy()) app.use(helmet.xssFilter()) app.use(helmet.originAgentCluster()) app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' })) app.use(helmet.crossOriginOpenerPolicy()) app.use(nocache()) app.use(compression({ threshold: 0 })) app.use(express.urlencoded({ limit: '50mb', extended: true })) app.use(express.json({ limit: '50mb' })) app.use(cors()) app.options('*', cors()) app.use(cookieParser(env.COOKIE_SECRET)) app.use(allowedMethods) app.use('/', agencyRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', propertyRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE helper.mkdir(env.CDN_USERS) helper.mkdir(env.CDN_TEMP_USERS) helper.mkdir(env.CDN_PROPERTIES) helper.mkdir(env.CDN_TEMP_PROPERTIES) helper.mkdir(env.CDN_LOCATIONS) helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
Wir verwenden React Lazy Loading, um jede Route zu laden.
Wir werden nicht jede Seite des Frontends behandeln, aber Sie können den Quellcode durchsuchen und jede einzelne sehen.
Mobile App
Die Plattform bietet eine native mobile App für Android und iOS. Die mobile App wurde mit React Native, Expo und TypeScript erstellt. Wie beim Frontend ermöglicht die mobile App dem Kunden, je nach Abhol- und Rückgabeort und -zeit nach verfügbaren Autos zu suchen, ein Auto auszuwählen und zur Kasse zu gehen.
Der Kunde erhält Push-Benachrichtigungen, wenn seine Buchung aus dem Backend aktualisiert wird. Push-Benachrichtigungen werden mit Node.js, Expo Server SDK und Firebase erstellt.
- ./mobile/assets/ Ordner enthält Bilder.
- ./mobile/screens/-Ordner enthält die Hauptbildschirme von React Native.
- ./mobile/components/ Ordner enthält React Native-Komponenten.
- ./mobile/services/ enthält API-Client-Dienste.
- ./mobile/App.tsx ist die wichtigste React Native App.
TypeScript-Typdefinitionen sind definiert in:
- ./mobile/types/index.d.ts
- ./mobile/types/env.d.ts
- ./mobile/miscellaneous/movininTypes.ts
./mobile/types/ wird wie folgt in ./mobile/tsconfig.json geladen:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
App.tsx ist der Haupteinstiegspunkt der React Native-App:
'react-native-gesture-handler' importieren importiere React, { useCallback, useEffect, useRef, useState } aus 'react' importiere { RootSiblingParent } aus 'react-native-root-siblings' import { NavigationContainer, NavigationContainerRef } aus '@react-navigation/native' importiere { StatusBar as ExpoStatusBar } aus 'expo-status-bar' importiere { SafeAreaProvider } aus 'react-native-safe-area-context' { Provider } aus 'react-native-paper' importieren * als SplashScreen aus 'expo-splash-screen' importieren * als Benachrichtigungen aus „expo-notifications“ importieren importiere { StripeProvider } aus '@stripe/stripe-react-native' SchubladeNavigator aus „./components/DrawerNavigator“ importieren * als Helfer aus './common/helper' importieren * als NotificationService aus './services/NotificationService' importieren * als UserService aus './services/UserService' importieren importiere { GlobalProvider } aus './context/GlobalContext' * als Umgebung aus „./config/env.config“ importieren Notifications.setNotificationHandler({ handleNotification: async () => ({ ShouldShowAlert: wahr, ShouldPlaySound: wahr, ShouldSetBadge: wahr, }), }) // // Verhindern Sie, dass der native Begrüßungsbildschirm vor der Deklaration der App-Komponente automatisch ausgeblendet wird // SplashScreen.preventAutoHideAsync() .then((result) => console.log(`SplashScreen.preventAutoHideAsync() erfolgreich: ${result}`)) .catch(console.warn) // Es ist gut, jeden Fehler explizit abzufangen und zu untersuchen const App = () => { const [appIsReady, setAppIsReady] = useState(false) const ResponseListener = useRef<notifications.subscription>() const navigationRef = useRef<navigationcontainerref>>(null) useEffect(() => { const register = async () => { const login = Warten auf UserService.loggedIn() if (eingeloggt) { const currentUser = Warten auf UserService.getCurrentUser() if (currentUser?._id) { Warten auf helper.registerPushToken(currentUser._id) } anders { helper.error() } } } // // Push-Benachrichtigungstoken registrieren // registrieren() // // Dieser Listener wird immer dann ausgelöst, wenn ein Benutzer auf eine Benachrichtigung tippt oder mit ihr interagiert (funktioniert, wenn die App im Vordergrund, Hintergrund oder beendet ist). // ResponseListener.current = Notifications.addNotificationResponseReceivedListener(async (response) => { versuchen { if (navigationRef.current) { const { data } = Response.notification.request.content if (data.booking) { if (data.user && data.notification) { Warten Sie auf NotificationService.markAsRead(data.user, [data.notification]) } navigationRef.current.navigate('Booking', { id: data.booking }) } anders { navigationRef.current.navigate('Benachrichtigungen', {}) } } } fangen (irrt) { helper.error(err, false) } }) return () => { Notifications.removeNotificationSubscription(responseListener.current!) } }, []) setTimeout(() => { setAppIsReady(true) }, 500) const onReady = useCallback(async () => { if (appIsReady) { // // Dies weist den Begrüßungsbildschirm an, sich sofort auszublenden! Wenn wir das nachrufen // `setAppIsReady`, dann sehen wir möglicherweise einen leeren Bildschirm, während die App läuft // seinen Anfangszustand laden und seine ersten Pixel rendern. Also stattdessen, // Wir blenden den Begrüßungsbildschirm aus, sobald wir wissen, dass die Root-Ansicht dies bereits getan hat // Layout ausgeführt. // Warten Sie auf SplashScreen.hideAsync() } }, [appIsReady]) if (!appIsReady) { null zurückgeben } zurückkehren ( <globalprovider> <safeareaprovider> <anbieter> <stripeprovider publizierbarkey="{env.STRIPE_PUBLISHABLE_KEY}" merchantidentifier="{env.STRIPE_MERCHANT_IDENTIFIER}"> <rootsiblingparent> <navigationcontainer ref="{navigationRef}" onready="{onReady}"> <expostatusbar> <p>Wir werden nicht jeden Bildschirm der mobilen App behandeln, aber Sie können den Quellcode durchsuchen und jeden einzelnen sehen.</p> <h2> Admin-Dashboard </h2> <p>Das Admin-Dashboard ist eine Webanwendung, die mit Node.js, React, MUI und TypeScript erstellt wurde. Vom Backend aus können Administratoren Lieferanten, Autos, Standorte, Kunden und Buchungen erstellen und verwalten. Wenn neue Lieferanten über das Backend erstellt werden, erhalten sie eine E-Mail mit der Aufforderung, ein Konto zu erstellen, um auf das Admin-Dashboard zuzugreifen und ihre Fahrzeugflotte und Buchungen zu verwalten.</p> <ul> <li>./backend/assets/ Ordner enthält CSS und Bilder.</li> <li>./backend/pages/ Ordner enthält React-Seiten.</li> <li>./backend/components/ Ordner enthält React-Komponenten.</li> <li>./backend/services/ enthält API-Client-Dienste.</li> <li>./backend/App.tsx ist die Haupt-React-App, die Routen enthält.</li> <li>./backend/index.tsx ist der Haupteinstiegspunkt des Admin-Dashboards.</li> </ul> <p>TypeScript-Typdefinitionen werden im Paket ./packages/movinin-types definiert.</p> <p>App.tsx des Admin-Dashboards folgt einer ähnlichen Logik wie App.tsx des Frontends.</p> <p>Wir werden nicht jede Seite des Admin-Dashboards behandeln, aber Sie können den Quellcode durchsuchen und jede einzelne sehen.</p> <h2> Sehenswürdigkeiten </h2> <p>Das Erstellen der mobilen App mit React Native und Expo ist sehr einfach. Expo macht die mobile Entwicklung mit React Native sehr einfach.</p> <p>Die Verwendung derselben Sprache (TypeScript) für Backend-, Frontend- und Mobilentwicklung ist sehr praktisch.</p> <p>TypeScript ist eine sehr interessante Sprache und hat viele Vorteile. Durch das Hinzufügen statischer Typisierung zu JavaScript können wir viele Fehler vermeiden und qualitativ hochwertigen, skalierbaren, besser lesbaren und wartbaren Code erstellen, der leicht zu debuggen und zu testen ist.</p> <p>Das ist es! Ich hoffe, dass Ihnen die Lektüre dieses Artikels gefallen hat.</p> <h2> Ressourcen </h2> <ol> <li>Übersicht</li> <li>Architektur</li> <li>Installation (selbst gehostet)</li> <li>Installieren (VPS)</li> <li> Installieren (Docker) <ol> <li>Docker-Image</li> <li>SSL</li> </ol> </li> <li>Stripe einrichten</li> <li>Mobile App erstellen</li> <li> Demo-Datenbank <ol> <li>Windows, Linux und macOS</li> <li>Docker</li> </ol> </li> <li>Von der Quelle ausführen</li> <li> Führen Sie die mobile App aus <ol> <li>Voraussetzungen</li> <li>Anleitung</li> <li>Push-Benachrichtigungen</li> </ol> </li> <li>Währung ändern</li> <li>Neue Sprache hinzufügen</li> <li>Unit-Tests und Abdeckung</li> <li>Protokolle</li> </ol> </expostatusbar></navigationcontainer></rootsiblingparent></stripeprovider></anbieter></safeareaprovider></globalprovider></navigationcontainerref></notifications.subscription>
Das obige ist der detaillierte Inhalt vonVon Null bis Storefront: Meine Reise zum Aufbau einer Immobilienvermietungsplattform. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Python eignet sich besser für Anfänger mit einer reibungslosen Lernkurve und einer kurzen Syntax. JavaScript ist für die Front-End-Entwicklung mit einer steilen Lernkurve und einer flexiblen Syntax geeignet. 1. Python-Syntax ist intuitiv und für die Entwicklung von Datenwissenschaften und Back-End-Entwicklung geeignet. 2. JavaScript ist flexibel und in Front-End- und serverseitiger Programmierung weit verbreitet.

Python und JavaScript haben ihre eigenen Vor- und Nachteile in Bezug auf Gemeinschaft, Bibliotheken und Ressourcen. 1) Die Python-Community ist freundlich und für Anfänger geeignet, aber die Front-End-Entwicklungsressourcen sind nicht so reich wie JavaScript. 2) Python ist leistungsstark in Bibliotheken für Datenwissenschaft und maschinelles Lernen, während JavaScript in Bibliotheken und Front-End-Entwicklungsbibliotheken und Frameworks besser ist. 3) Beide haben reichhaltige Lernressourcen, aber Python eignet sich zum Beginn der offiziellen Dokumente, während JavaScript mit Mdnwebdocs besser ist. Die Wahl sollte auf Projektbedürfnissen und persönlichen Interessen beruhen.

Die Verschiebung von C/C zu JavaScript erfordert die Anpassung an dynamische Typisierung, Müllsammlung und asynchrone Programmierung. 1) C/C ist eine statisch typisierte Sprache, die eine manuelle Speicherverwaltung erfordert, während JavaScript dynamisch eingegeben und die Müllsammlung automatisch verarbeitet wird. 2) C/C muss in den Maschinencode kompiliert werden, während JavaScript eine interpretierte Sprache ist. 3) JavaScript führt Konzepte wie Verschlüsse, Prototypketten und Versprechen ein, die die Flexibilität und asynchrone Programmierfunktionen verbessern.

Unterschiedliche JavaScript -Motoren haben unterschiedliche Auswirkungen beim Analysieren und Ausführen von JavaScript -Code, da sich die Implementierungsprinzipien und Optimierungsstrategien jeder Engine unterscheiden. 1. Lexikalanalyse: Quellcode in die lexikalische Einheit umwandeln. 2. Grammatikanalyse: Erzeugen Sie einen abstrakten Syntaxbaum. 3. Optimierung und Kompilierung: Generieren Sie den Maschinencode über den JIT -Compiler. 4. Führen Sie aus: Führen Sie den Maschinencode aus. V8 Engine optimiert durch sofortige Kompilierung und versteckte Klasse.

Zu den Anwendungen von JavaScript in der realen Welt gehören die serverseitige Programmierung, die Entwicklung mobiler Anwendungen und das Internet der Dinge. Die serverseitige Programmierung wird über node.js realisiert, die für die hohe gleichzeitige Anfrageverarbeitung geeignet sind. 2. Die Entwicklung der mobilen Anwendungen erfolgt durch reaktnative und unterstützt die plattformübergreifende Bereitstellung. 3.. Wird für die Steuerung von IoT-Geräten über die Johnny-Five-Bibliothek verwendet, geeignet für Hardware-Interaktion.

Ich habe eine funktionale SaaS-Anwendung mit mehreren Mandanten (eine EdTech-App) mit Ihrem täglichen Tech-Tool erstellt und Sie können dasselbe tun. Was ist eine SaaS-Anwendung mit mehreren Mietern? Mit Multi-Tenant-SaaS-Anwendungen können Sie mehrere Kunden aus einem Sing bedienen

Dieser Artikel zeigt die Frontend -Integration mit einem Backend, das durch die Genehmigung gesichert ist und eine funktionale edtech SaaS -Anwendung unter Verwendung von Next.js. erstellt. Die Frontend erfasst Benutzerberechtigungen zur Steuerung der UI-Sichtbarkeit und stellt sicher, dass API-Anfragen die Rollenbasis einhalten

JavaScript ist die Kernsprache der modernen Webentwicklung und wird für seine Vielfalt und Flexibilität häufig verwendet. 1) Front-End-Entwicklung: Erstellen Sie dynamische Webseiten und einseitige Anwendungen durch DOM-Operationen und moderne Rahmenbedingungen (wie React, Vue.js, Angular). 2) Serverseitige Entwicklung: Node.js verwendet ein nicht blockierendes E/A-Modell, um hohe Parallelitäts- und Echtzeitanwendungen zu verarbeiten. 3) Entwicklung von Mobil- und Desktop-Anwendungen: Die plattformübergreifende Entwicklung wird durch reaktnative und elektronen zur Verbesserung der Entwicklungseffizienz realisiert.


Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

AI Hentai Generator
Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

Heiße Werkzeuge

mPDF
mPDF ist eine PHP-Bibliothek, die PDF-Dateien aus UTF-8-codiertem HTML generieren kann. Der ursprüngliche Autor, Ian Back, hat mPDF geschrieben, um PDF-Dateien „on the fly“ von seiner Website auszugeben und verschiedene Sprachen zu verarbeiten. Es ist langsamer und erzeugt bei der Verwendung von Unicode-Schriftarten größere Dateien als Originalskripte wie HTML2FPDF, unterstützt aber CSS-Stile usw. und verfügt über viele Verbesserungen. Unterstützt fast alle Sprachen, einschließlich RTL (Arabisch und Hebräisch) und CJK (Chinesisch, Japanisch und Koreanisch). Unterstützt verschachtelte Elemente auf Blockebene (wie P, DIV),

SAP NetWeaver Server-Adapter für Eclipse
Integrieren Sie Eclipse mit dem SAP NetWeaver-Anwendungsserver.

WebStorm-Mac-Version
Nützliche JavaScript-Entwicklungstools

MinGW – Minimalistisches GNU für Windows
Dieses Projekt wird derzeit auf osdn.net/projects/mingw migriert. Sie können uns dort weiterhin folgen. MinGW: Eine native Windows-Portierung der GNU Compiler Collection (GCC), frei verteilbare Importbibliotheken und Header-Dateien zum Erstellen nativer Windows-Anwendungen, einschließlich Erweiterungen der MSVC-Laufzeit zur Unterstützung der C99-Funktionalität. Die gesamte MinGW-Software kann auf 64-Bit-Windows-Plattformen ausgeführt werden.

VSCode Windows 64-Bit-Download
Ein kostenloser und leistungsstarker IDE-Editor von Microsoft