Quellcode: https://github.com/aelassas/bookcars
Demo: https://bookcars.dynv6.net:3002
Die Idee entstand aus dem Wunsch heraus, ohne Grenzen zu bauen – eine vollständig anpassbare Autovermietungs-Website und mobile App, bei der jeder Aspekt in Ihrer Kontrolle liegt:
Hier ist der Tech-Stack, der es möglich gemacht hat:
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.
React zeichnet sich durch Folgendes als ausgezeichnete Wahl aus:
In diesem Abschnitt sehen Sie die Hauptseiten des Frontends, des Admin-Dashboards und der mobilen App.
Vom Frontend aus kann der Benutzer nach verfügbaren Autos suchen, ein Auto auswählen und zur Kasse gehen.
Unten befindet sich die Hauptseite des Frontends, auf der der Benutzer Abhol- und Abgabepunkte und -zeiten auswählen und nach verfügbaren Autos suchen kann.
Unten finden Sie das Suchergebnis der Hauptseite, auf der der Benutzer ein Auto zum Mieten auswählen kann.
Unten befindet sich die Checkout-Seite, auf der der Benutzer Mietoptionen und Checkout festlegen kann. Wenn der Benutzer nicht registriert ist, kann er gleichzeitig zur Kasse gehen und sich 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 befindet sich die Seite, auf der der Benutzer seine Buchungen sehen und verwalten kann.
Unten befindet sich die Seite, auf der der Benutzer eine Buchung im Detail sehen kann.
Unten finden Sie die Kontaktseite.
Unten finden Sie die Seite mit den Autovermietungsstandorten.
Unten finden Sie die Seite, auf der der Kunde seine Benachrichtigungen sehen und verwalten kann.
Es gibt noch andere Seiten, aber dies sind die Hauptseiten des Frontends.
Es gibt drei Arten von Benutzern:
Die Plattform ist für die Zusammenarbeit mit mehreren Lieferanten konzipiert. Jeder Lieferant kann seine Fahrzeugflotte und Buchungen über das Admin-Dashboard verwalten. Die Plattform kann auch mit nur einem Lieferanten zusammenarbeiten.
Über das Admin-Dashboard kann der Admin-Benutzer Lieferanten, Autos, Standorte, Benutzer und Buchungen erstellen und verwalten.
Wenn der Admin-Benutzer einen neuen Lieferanten erstellt, erhält der Lieferant automatisch eine E-Mail zur Erstellung seines Kontos, um auf das Admin-Dashboard zuzugreifen, damit er seine Fahrzeugflotte und Buchungen verwalten kann.
Unten finden Sie die Anmeldeseite des Admin-Dashboards.
Unten finden Sie die Dashboard-Seite des Admin-Dashboards, auf der Administratoren und Lieferanten Buchungen sehen und verwalten können.
Unten finden Sie die Seite, auf der die Fahrzeugflotte angezeigt und verwaltet werden kann.
Unten finden Sie die Seite, auf der Administratoren und Lieferanten neue Autos erstellen können, indem sie ein Bild und Fahrzeuginformationen bereitstellen. Damit die Fahrzeugoptionen kostenlos enthalten sind, stellen Sie für die entsprechende Fahrzeugoption den Wert 0 ein. Andernfalls legen Sie den Preis der Option fest oder lassen Sie das Feld leer, wenn Sie sie nicht einschließen möchten.
Unten finden Sie die Seite, auf der Administratoren und Lieferanten Autos bearbeiten können.
Unten finden Sie die Seite, auf der Administratoren Plattformbenutzer verwalten können.
Unten finden Sie die Seite, auf der Sie Buchungen bearbeiten können.
Es gibt noch andere Seiten, aber dies sind die Hauptseiten des Admin-Dashboards.
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 Autos, Buchungen und Kunden, und andere, die keine Authentifizierung erfordern, wie zum Beispiel das Abrufen von Standorten und verfügbaren Autos für nicht authentifizierte Benutzer:
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 * as env from './config/env.config' import * as databaseHelper from './common/databaseHelper' import app from './app' 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 supplierRoutes from './routes/supplierRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import carRoutes from './routes/carRoutes' 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('/', supplierRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', carRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE await helper.mkdir(env.CDN_USERS) await helper.mkdir(env.CDN_TEMP_USERS) await helper.mkdir(env.CDN_CARS) await helper.mkdir(env.CDN_TEMP_CARS) await helper.mkdir(env.CDN_LOCATIONS) await 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“, „carRoutes“ 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:
Wir werden nicht jede Route einzeln erklären. Wir nehmen zum Beispiel „countryRoutes“ und sehen, wie es erstellt wurde:
import express from 'express' import routeNames from '../config/countryRoutes.config' import authJwt from '../middlewares/authJwt' import * as countryController from '../controllers/countryController' const routes = express.Router() routes.route(routeNames.validate).post(authJwt.verifyToken, countryController.validate) routes.route(routeNames.create).post(authJwt.verifyToken, countryController.create) routes.route(routeNames.update).put(authJwt.verifyToken, countryController.update) routes.route(routeNames.delete).delete(authJwt.verifyToken, countryController.deleteCountry) routes.route(routeNames.getCountry).get(authJwt.verifyToken, countryController.getCountry) routes.route(routeNames.getCountries).get(authJwt.verifyToken, countryController.getCountries) routes.route(routeNames.getCountriesWithLocations).get(countryController.getCountriesWithLocations) routes.route(routeNames.checkCountry).get(authJwt.verifyToken, countryController.checkCountry) routes.route(routeNames.getCountryId).get(authJwt.verifyToken, countryController.getCountryId) 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 die Routennamen von countryRoutes:
const routes = { validate: '/api/validate-country', create: '/api/create-country', update: '/api/update-country/:id', delete: '/api/delete-country/:id', getCountry: '/api/country/:id/:language', getCountries: '/api/countries/:page/:size/:language', getCountriesWithLocations: '/api/countries-with-locations/:language/:imageRequired/:minLocations', checkCountry: '/api/check-country/:id', getCountryId: '/api/country-id/:name/:language', } export default routes
countryController enthält die Hauptgeschäftslogik in Bezug auf Länder. 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 Ländermodell:
import { Schema, model } from 'mongoose' import * as env from '../config/env.config' const countrySchema = new Schema<env.Country>( { values: { type: [Schema.Types.ObjectId], ref: 'LocationValue', required: [true, "can't be blank"], validate: (value: any): boolean => Array.isArray(value), }, }, { timestamps: true, strict: true, collection: 'Country', }, ) const Country = model<env.Country>('Country', countrySchema) export default Country
Unten ist der TypeScript-Typ env.Country:
export interface Country extends Document { values: Types.ObjectId[] name?: string }
Ein Land hat mehrere Werte. Eine pro Sprache. Standardmäßig werden Englisch und Französisch unterstützt.
Unten ist das LocationValue-Modell:
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 * as env from './config/env.config' import * as databaseHelper from './common/databaseHelper' import app from './app' 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)) }
Unten ist der TypeScript-Typ env.LocationValue:
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 supplierRoutes from './routes/supplierRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import carRoutes from './routes/carRoutes' 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('/', supplierRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', carRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE await helper.mkdir(env.CDN_USERS) await helper.mkdir(env.CDN_TEMP_USERS) await helper.mkdir(env.CDN_CARS) await helper.mkdir(env.CDN_TEMP_CARS) await helper.mkdir(env.CDN_LOCATIONS) await helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
Ein LocationValue hat einen Sprachcode (ISO 639-1) und einen Zeichenfolgenwert.
Unten finden Sie die Funktion zum Erstellen eines Controllers:
import express from 'express' import routeNames from '../config/countryRoutes.config' import authJwt from '../middlewares/authJwt' import * as countryController from '../controllers/countryController' const routes = express.Router() routes.route(routeNames.validate).post(authJwt.verifyToken, countryController.validate) routes.route(routeNames.create).post(authJwt.verifyToken, countryController.create) routes.route(routeNames.update).put(authJwt.verifyToken, countryController.update) routes.route(routeNames.delete).delete(authJwt.verifyToken, countryController.deleteCountry) routes.route(routeNames.getCountry).get(authJwt.verifyToken, countryController.getCountry) routes.route(routeNames.getCountries).get(authJwt.verifyToken, countryController.getCountries) routes.route(routeNames.getCountriesWithLocations).get(countryController.getCountriesWithLocations) routes.route(routeNames.checkCountry).get(authJwt.verifyToken, countryController.checkCountry) routes.route(routeNames.getCountryId).get(authJwt.verifyToken, countryController.getCountryId) export default routes
In dieser Funktion rufen wir den Hauptteil der Anfrage ab, durchlaufen die im Hauptteil bereitgestellten Werte (ein Wert pro Sprache) und erstellen einen LocationValue. Abschließend erstellen wir das Land abhängig von den erstellten Standortwerten.
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:
TypeScript-Typdefinitionen werden im Paket ./packages/bookcars-types definiert.
App.tsx ist die Hauptreaktions-App:
const routes = { validate: '/api/validate-country', create: '/api/create-country', update: '/api/update-country/:id', delete: '/api/delete-country/:id', getCountry: '/api/country/:id/:language', getCountries: '/api/countries/:page/:size/:language', getCountriesWithLocations: '/api/countries-with-locations/:language/:imageRequired/:minLocations', checkCountry: '/api/check-country/:id', getCountryId: '/api/country-id/:name/:language', } export default routes
Wir verwenden React Lazy Loading, um jede Route zu laden.
Wir werden nicht jede Seite des Frontends behandeln, aber Sie können den Quellcode öffnen und jede einzelne Seite sehen, wenn Sie möchten.
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.
TypeScript-Typdefinitionen sind definiert in:
./mobile/types/ wird wie folgt in ./mobile/tsconfig.json geladen:
import { Schema, model } from 'mongoose' import * as env from '../config/env.config' const countrySchema = new Schema<env.Country>( { values: { type: [Schema.Types.ObjectId], ref: 'LocationValue', required: [true, "can't be blank"], validate: (value: any): boolean => Array.isArray(value), }, }, { timestamps: true, strict: true, collection: 'Country', }, ) const Country = model<env.Country>('Country', countrySchema) export default Country
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: true, }), }) // // Verhindern, 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<StackParams>>(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, im 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', {}) } } } Catch (Err) { 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 öffnen und jeden einzelnen Bildschirm sehen, wenn Sie möchten.</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 Backend zuzugreifen und ihre Fahrzeugflotte und Buchungen zu verwalten.</p>
TypeScript-Typdefinitionen werden im Paket ./packages/bookcars-types definiert.
App.tsx des Backends folgt einer ähnlichen Logik wie App.tsx des Frontends.
Wir werden nicht jede Seite des Admin-Dashboards behandeln, aber Sie können den Quellcode öffnen und jede einzelne Seite sehen, wenn Sie möchten.
Das Erstellen der mobilen App mit React Native und Expo ist sehr einfach. Expo macht die mobile Entwicklung mit React Native sehr einfach.
Die Verwendung derselben Sprache (TypeScript) für Backend-, Frontend- und Mobilentwicklung ist sehr praktisch.
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.
Das ist es! Ich hoffe, dass Ihnen die Lektüre dieses Artikels gefallen hat.
Das obige ist der detaillierte Inhalt vonVon Null bis Storefront: Meine Reise zum Aufbau einer Autovermietungs-Website und einer mobilen App. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!