Vehicle-rental-system: DriveHive: The Vanilla Full-Stack Rental App That Hides a Serious Systems Design
A vehicle-rental platform built with plain HTML, vanilla JavaScript, Node, MongoDB, JWT, Cloudinary, and SMTP fallbacks, where the real story is the resilience behind the UI.
- DriveHive feels production-minded because it treats missing infrastructure as a design constraint, not a blocker.
- The backend keeps business rules close to the request path while the frontend manages its own state without a framework.
- Its fallback logic for images, resets, and sessions is the clearest sign that this repo is built to keep moving under imperfect conditions.
- The codebase sits in advanced-prototype territory, which makes its discipline more interesting than any polished UI could.
Most rental demos stop at listings and login screens. DriveHive keeps going, because the system has to survive real gaps: broken images, missing SMTP, cached sessions, and authenticated flows that still need to behave when the happy path is unavailable.
The app that keeps working when pieces are missing
That is the real hook. DriveHive is a full-stack app that behaves like a product team built it for operations, not for a portfolio. It uses plain HTML, vanilla JavaScript, Express, Mongoose, JWT, Cloudinary, and Nodemailer, but the more important detail is how it degrades gracefully when one of those pieces is absent.
Java-based project implementing a Vehicle Rental System, featuring a flexible class hierarchy for cars and motorcycles. The system, built using object-oriented principles, includes a central "RentalSystem" for inventory management and rental operations.
The real trick is not the rental flow. It is the fallback logic
Three choices define the repo’s design philosophy. First, vehicle photos can resolve from Cloudinary, a local path, or a placeholder when data is incomplete. Second, password resets can still be generated in development when SMTP is unavailable. Third, the frontend persists identity and cache state in localStorage so the app can pick up where the user left off.
A backend that splits business rules from HTTP
The backend uses a service-controller split that keeps the messy parts in the right place. `authService.js` owns logic like registration validation and password reset workflows, while `authController.js` turns those outcomes into HTTP responses. That separation matters because the business rules stay testable even when the transport layer changes.
// authService.js
async function requestPasswordReset(email) {
const user = await User.findOne({ email });
if (!user) throw new AuthError('User not found', 404);
const resetToken = generateResetToken(user._id);
const resetLink = `${BASE_URL}/reset-password?token=${resetToken}`;
if (!smtpConfigured()) {
return { resetLink, devFallback: true };
}
await sendResetEmail(user.email, resetLink);
return { success: true };
}
// authController.js
async function requestPasswordResetController(req, res) {
try {
const result = await authService.requestPasswordReset(req.body.email);
return res.status(200).json(result);
} catch (err) {
return res.status(err.statusCode || 500).json({ message: err.message });
}
}
The same pattern shows up in request validation. The server keeps rules like allowed vehicle years and other business constraints close to the entry point, which avoids turning the browser into a second source of truth.
| Layer | DriveHive approach | Why it matters |
|---|---|---|
| Validation | Backend checks request shape and business rules | The browser cannot silently invent policy |
| Workflow | Service layer handles logic, controller handles responses | Code stays easier to extend and test |
| Identity | JWT verification attaches the user to the request | Protected routes stay explicit |
| Failure mode | Missing SMTP or image source triggers a fallback | The app stays usable instead of collapsing |
The frontend is a stateful machine, not just pages
The client side does real orchestration. `app.js` maintains a `vehiclesCache`, reads and writes `localStorage`, renders vehicle cards dynamically, and uses `escapeHtml` to reduce the risk of unsafe content reaching the DOM. Even without React or a build pipeline, the frontend is acting like a state manager.
const vehiclesCache = [];
function resolveVehicleImageUrl(vehicle) {
if (vehicle.imageUrl && vehicle.imageUrl.startsWith('https://res.cloudinary.com')) {
return vehicle.imageUrl;
}
if (vehicle.imageUrl && vehicle.imageUrl.startsWith('/')) {
return vehicle.imageUrl;
}
return '/assets/img/placeholder-vehicle.jpg';
}
function renderRedesignVehicleCard(vehicle) {
const imageUrl = resolveVehicleImageUrl(vehicle);
return `
<article class="glass-surface vehicle-card">
<img src="${imageUrl}" alt="${escapeHtml(vehicle.name)}">
<h3>${escapeHtml(vehicle.name)}</h3>
<p>${formatINR(vehicle.pricePerDay)} per day</p>
</article>
`;
}
| Frontend pattern | DriveHive | Framework-heavy app |
|---|---|---|
| State | Manual cache plus localStorage | Centralized component state |
| Rendering | Template strings and direct DOM updates | Virtual DOM and component trees |
| Tooling | Script tags and static assets | Bundler, router, and build chain |
| Trade-off | Less ceremony, more manual coordination | More structure, more moving parts |
The redesign files tell you this repo is mid-migration
The coexistence of legacy and redesigned files is not a flaw. It is evidence of an active product evolution. The backend keeps running while the visual layer changes shape, which is exactly how a real app moves when nobody gets to stop the world for a rewrite.
| Signal | What it suggests | Editorial read |
|---|---|---|
| Legacy and redesign files side by side | An in-progress UI migration | The app is alive, not archived |
| Heavy use of raw HTML and script tags | A lightweight deployment model | Speed and simplicity over framework ceremony |
| Repeated utility functions in client code | A codebase that is still stabilizing | Useful, but not yet fully hardened |
That puts the project in an honest middle ground. It has the bones of a production system, but the raw structure, direct DOM injection, and migration artifacts still read as an advanced prototype.
How DriveHive compares to heavier rental stacks
| Approach | Stack | Complexity center | Best fit |
|---|---|---|---|
| DriveHive | Vanilla JS, Node, MongoDB | Operational resilience and business rules | A focused web app that needs control without framework overhead |
| Framework-heavy MERN app | React, Express, MongoDB, Node | Component architecture and client build tooling | Teams that want a more standardized frontend system |
| Mobile-first rental product | Flutter or native mobile stack | App distribution and device integration | Marketplace products optimized for phones and maps |
| CLI educational project | Pure Java | OOP modeling and console I/O | Teaching core logic without infrastructure |
The trade-off is simple. DriveHive gives up some of the guardrails of a framework-heavy stack, but it gains speed, transparency, and a smaller dependency surface. That makes it a strong example of how far careful plain-JS architecture can go.





