June 2026
NovaDrive was my backend exam project in the second year of MCT at Howest. The brief was to build the backend of a robotaxi company: no drivers, just a fleet of autonomous cars and the platform that connects them to passengers.
The solution has four parts. NovaDrive.Api is the ASP.NET Core backend and exposes REST, GraphQL and gRPC. NovaDrive.Blazor is the web front end, with a passenger side and an admin side. NovaDrive.Client is a console app that simulates vehicles driving and sending telemetry. NovaDrive.test holds the unit and integration tests.
The platform serves three kinds of users. Passengers book and track rides. Admins manage vehicles, maintenance, revenue and support. The vehicles themselves report their position, battery level and hardware health.
The biggest challenge was that the platform serves three very different clients, and each needs its own level of trust. Passengers and admins are people who log in and should only see their own part of the system. Vehicles are machines that need a separate, secure way to identify themselves without a login screen.
The data had the same problem. Rides, payments and invoices need strict relations and consistency. Telemetry arrives every two seconds per vehicle and has a much looser structure. Forcing both into one database would have been a poor fit for at least one of them. That constant stream of vehicle updates also needed a faster, lighter channel than ordinary JSON over HTTP.
Pricing was a challenge of its own. A fare combines distance, time, vehicle type, a night surcharge, loyalty points, discount codes, 21% Belgian VAT and a minimum fare. The order of these steps and the rounding both change the final amount, and the passenger's invoice has to show how the price was built.
Finally, the project changed direction halfway through. The first version used local ASP.NET Identity accounts. Partway through, I moved everything to Auth0 without losing the existing data.
The API is split into layers: minimal-API endpoint groups, services, repositories and DTOs. AutoMapper maps entities to DTOs, FluentValidation checks every incoming request with 14 validators, and Serilog writes logs to the console and to daily rolling log files.
Each of the three protocols has a clear job. REST, with 33 endpoints, covers the passenger flow: registration, profile, booking, cancelling, discount codes, payments and support tickets. GraphQL, built with Hot Chocolate, is admin-only and lets the dashboard query vehicles, rides, payments and tickets with filtering and sorting, and run mutations such as creating vehicles or generating API keys. gRPC runs on its own HTTP/2 port and receives vehicle telemetry through a Protobuf contract.
For storage I used two databases. PostgreSQL, accessed through Entity Framework Core, stores users, passengers, vehicles, rides, payments, discount codes, maintenance logs and support tickets, and migrations run automatically at startup. MongoDB stores telemetry points and sensor diagnostics, such as Lidar, Radar and Camera errors along with their raw data. Docker Compose starts both databases together with Mailpit for testing email.
People log in through Auth0 with JWT bearer tokens, and custom role claims drive the PassengerOnly and AdminOnly policies. When a passenger registers, the API creates the account in Auth0 through the Management API and assigns the Passenger role automatically. Vehicles authenticate with API keys that start with "nvd_". Each key is generated with a cryptographic random generator and shown only once. The database stores only its SHA-256 hash, and custom middleware or the gRPC service checks it on every request.
When a passenger requests a ride, the API reads the latest GPS point of every active vehicle from MongoDB and uses the Haversine formula to assign the nearest one. When the ride is completed, the pricing engine runs, loyalty points are updated and the vehicle's mileage is increased. A payment is then created automatically, and a PDF invoice is generated with QuestPDF and emailed to the passenger with MailKit.
The pricing engine works in seven steps. It starts with a base fare of €2.50 plus €1.10 per kilometre and €0.30 per minute. It then applies a multiplier for the vehicle type: ×1.0 for Standard, ×1.5 for a Van and ×2.2 for Luxury. Rides between 22:00 and 06:00 get a 15% night surcharge. Passengers can spend loyalty points at €1 per 100 points, capped at 20% of the fare. A discount code can then take off a percentage or a fixed amount, after checks for expiry and minimum ride value. After that the engine adds 21% VAT, enforces a €5 minimum fare and rounds the result. Every step is saved as a JSON breakdown on the ride, so the invoice can show exactly how the price was built.
The Blazor Server front end uses MudBlazor and logs in through Auth0 with OpenID Connect and PKCE. Admins get a dashboard, a live fleet map built with Leaflet and OpenStreetMap, vehicle management, maintenance logs, a rides and revenue overview, and support tickets. Passengers can book a ride, with addresses turned into coordinates automatically through Nominatim. They can also follow the ride on a map, view their ride history, manage their profile and contact support.
The console simulator logs in as an admin, loads the fleet and then simulates several cars at once. Each car drives a route through Antwerp and streams its position, speed, battery level and temperature over gRPC.
The pricing engine is covered by 14 unit tests that use an in-memory repository. Another 9 integration tests use Testcontainers to start a real PostgreSQL database in Docker. On top of that, .http files cover the full admin, passenger, support and telemetry flows.
The result is a working end-to-end platform. A passenger can register, book a ride, be matched to the nearest car, watch it on a map and receive an itemised PDF invoice by email. Meanwhile, admins can see every vehicle move live on the fleet map. For example, a 10 km, 15-minute daytime ride in a Standard car costs €18.00 excl. VAT plus €3.78 VAT, for a total of €21.78. The tests check that exact amount.
In total the platform uses three API protocols, two databases and three authentication models. It has 33 REST endpoints, 14 request validators, 23 automated tests and 13 Blazor pages, and it starts locally with a single docker compose up.
The project taught me the most about choosing the right tool for each part of a system: GraphQL where flexibility matters, gRPC where speed matters and MongoDB where the data is high-volume and loosely structured. It also taught me how to move a running project to a new authentication provider without breaking it.