276 lines
10 KiB
Markdown
276 lines
10 KiB
Markdown
Laravel/PHP is **not obsolete**—Laravel 13 is current, actively maintained, and still a perfectly reasonable choice. Laravel releases annually, with bug fixes for 18 months and security fixes for two years. ([Laravel][1])
|
||
|
||
For your application, though—database, MQTT-connected devices, live status, APIs, and likely a web dashboard—I would lean toward a **TypeScript stack using NestJS**.
|
||
|
||
## My recommendation
|
||
|
||
```text
|
||
Web UI: React + Next.js
|
||
Backend API: NestJS + TypeScript
|
||
Database: PostgreSQL
|
||
Database ORM: Prisma
|
||
MQTT broker: Mosquitto initially; EMQX if you outgrow it
|
||
MQTT processor: Separate NestJS worker process
|
||
Live browser UI: WebSockets or Server-Sent Events
|
||
Deployment: Docker Compose initially
|
||
Reverse proxy: Caddy, Traefik, or nginx
|
||
```
|
||
|
||
NestJS is particularly suitable because it supports conventional HTTP APIs, WebSockets, background services, and MQTT transport within one consistent framework. A Nest application can also operate as a “hybrid application,” listening for HTTP requests and messages from another transport. ([NestJS Documentation][2])
|
||
|
||
### Suggested architecture
|
||
|
||
```text
|
||
┌────────────────────┐
|
||
│ React / Next.js UI │
|
||
└─────────┬──────────┘
|
||
│ HTTPS / WebSocket
|
||
┌─────────▼──────────┐
|
||
│ NestJS API │
|
||
│ Auth, users, maps, │
|
||
│ devices, tickets │
|
||
└─────────┬──────────┘
|
||
│
|
||
┌──────▼──────┐
|
||
│ PostgreSQL │
|
||
└─────────────┘
|
||
|
||
MagLink / Receivers
|
||
│
|
||
│ MQTT over TLS
|
||
▼
|
||
┌──────────────────┐ ┌─────────────────────┐
|
||
│ MQTT Broker │──────▶│ MQTT Worker │
|
||
│ Mosquitto / EMQX │ │ validate, store, │
|
||
└──────────────────┘ │ acknowledge, alert │
|
||
└──────────┬──────────┘
|
||
│
|
||
PostgreSQL / API
|
||
```
|
||
|
||
I would **not** have the web API itself be the MQTT broker. Keep these separate:
|
||
|
||
* The broker handles connections, sessions, subscriptions, retained messages, QoS, and TLS.
|
||
* Your worker subscribes to device topics, validates messages, writes database records, and publishes acknowledgments.
|
||
* Your API handles users, configuration, history, maps, device management, and commands.
|
||
* The browser receives live updates through WebSockets or Server-Sent Events—not directly from the device MQTT namespace.
|
||
|
||
This separation means restarting or deploying the web application does not disconnect every field device.
|
||
|
||
## Why NestJS is a good fit
|
||
|
||
NestJS feels somewhat like Laravel:
|
||
|
||
| Laravel concept | NestJS equivalent |
|
||
| ----------------- | --------------------------- |
|
||
| Controllers | Controllers |
|
||
| Middleware | Middleware |
|
||
| Service container | Dependency injection |
|
||
| Artisan commands | CLI commands/scripts |
|
||
| Eloquent models | Prisma models |
|
||
| Queued jobs | Worker processes/queues |
|
||
| Events/listeners | Events and message handlers |
|
||
| Laravel Echo | WebSocket gateways |
|
||
|
||
Because everything is TypeScript, you can share types between the browser, API, and MQTT message definitions. That is particularly useful for your MagLink JSON payloads:
|
||
|
||
```typescript
|
||
export interface LocateRecord {
|
||
messageId: string;
|
||
deviceSerial: string;
|
||
timestamp: string;
|
||
latitude: number;
|
||
longitude: number;
|
||
altitudeM?: number;
|
||
fixType: "NONE" | "GPS" | "DGPS" | "RTK_FLOAT" | "RTK_FIXED";
|
||
depthCm?: number;
|
||
frequencyHz?: number;
|
||
gain?: number;
|
||
signalStrength?: number;
|
||
tiltDeg?: number;
|
||
ticketNumber?: string;
|
||
}
|
||
```
|
||
|
||
You can then validate the runtime payload with Zod or NestJS validation before inserting it into PostgreSQL.
|
||
|
||
## Don’t begin with microservices
|
||
|
||
I would start with one repository and three deployable processes:
|
||
|
||
```text
|
||
apps/
|
||
web/ Next.js frontend
|
||
api/ NestJS HTTP/WebSocket API
|
||
mqtt-worker/ NestJS MQTT subscriber
|
||
|
||
packages/
|
||
database/ Prisma schema and client
|
||
contracts/ Shared DTOs and MQTT schemas
|
||
common/ Shared utilities
|
||
```
|
||
|
||
They can all use the same PostgreSQL database at first. This gives you clear boundaries without introducing Kubernetes, Kafka, service discovery, distributed tracing, or multiple databases prematurely.
|
||
|
||
Run them locally with Docker Compose:
|
||
|
||
```yaml
|
||
services:
|
||
postgres:
|
||
image: postgres:17
|
||
environment:
|
||
POSTGRES_DB: maglink
|
||
POSTGRES_USER: maglink
|
||
POSTGRES_PASSWORD: development
|
||
volumes:
|
||
- postgres-data:/var/lib/postgresql/data
|
||
|
||
mosquitto:
|
||
image: eclipse-mosquitto:2
|
||
ports:
|
||
- "1883:1883"
|
||
- "8883:8883"
|
||
volumes:
|
||
- ./infrastructure/mosquitto:/mosquitto/config
|
||
|
||
api:
|
||
build: .
|
||
command: npm run start:api
|
||
depends_on:
|
||
- postgres
|
||
|
||
mqtt-worker:
|
||
build: .
|
||
command: npm run start:mqtt-worker
|
||
depends_on:
|
||
- postgres
|
||
- mosquitto
|
||
|
||
web:
|
||
build: .
|
||
command: npm run start:web
|
||
depends_on:
|
||
- api
|
||
|
||
volumes:
|
||
postgres-data:
|
||
```
|
||
|
||
## MQTT design I’d use
|
||
|
||
For your devices, use topics such as:
|
||
|
||
```text
|
||
devices/{serial}/telemetry
|
||
devices/{serial}/locates
|
||
devices/{serial}/status
|
||
devices/{serial}/commands
|
||
devices/{serial}/commands/ack
|
||
```
|
||
|
||
Every important device message should contain:
|
||
|
||
```json
|
||
{
|
||
"messageId": "01JZ...",
|
||
"schemaVersion": 1,
|
||
"deviceSerial": "ML-001234",
|
||
"timestamp": "2026-07-11T21:50:00Z",
|
||
"payload": {}
|
||
}
|
||
```
|
||
|
||
The server should enforce a unique database constraint on `messageId`. That gives you idempotency: if MQTT QoS causes redelivery, the message is not stored twice.
|
||
|
||
For important records:
|
||
|
||
1. Device publishes with QoS 1.
|
||
2. Worker validates and stores the record.
|
||
3. Database transaction commits.
|
||
4. Worker publishes an application-level acknowledgment containing the original `messageId`.
|
||
5. Device retains the local record until it receives that acknowledgment.
|
||
|
||
MQTT QoS confirms broker delivery; your application acknowledgment confirms that your system actually accepted and persisted the record.
|
||
|
||
## Authentication
|
||
|
||
Use two separate security systems:
|
||
|
||
**Web users**
|
||
|
||
* Secure HTTP-only session cookies
|
||
* OIDC/OAuth later if customers need Microsoft or Google login
|
||
* Organization-based access control in the database
|
||
|
||
**Devices**
|
||
|
||
* TLS from the beginning
|
||
* Ideally one client certificate per device
|
||
* Broker ACLs that restrict each device to its own topic hierarchy
|
||
* Never embed a common fleet-wide MQTT password in every unit
|
||
|
||
## Other good options
|
||
|
||
### ASP.NET Core
|
||
|
||
This would also be an excellent choice for you, especially since you already work with .NET MAUI. ASP.NET Core supports long-running hosted services, and MQTTnet provides both MQTT client and broker functionality with MQTT 5 support. ([Dotnet][3])
|
||
|
||
A good .NET version would be:
|
||
|
||
```text
|
||
ASP.NET Core Web API
|
||
Entity Framework Core
|
||
PostgreSQL
|
||
MQTTnet client
|
||
React or Blazor frontend
|
||
```
|
||
|
||
I would choose this over NestJS when:
|
||
|
||
* You prefer C# over JavaScript/TypeScript.
|
||
* The server will contain substantial business logic.
|
||
* You expect close integration with your MAUI application.
|
||
* Long-term static typing and compiler enforcement matter more than frontend/backend language sharing.
|
||
|
||
Honestly, **ASP.NET Core may be the strongest fit for your background**. NestJS is the more fashionable web stack, but ASP.NET Core is modern, mature, fast, strongly typed, and exceptionally well supported.
|
||
|
||
### Modern Laravel
|
||
|
||
Staying with Laravel would be the fastest path if you already know it well:
|
||
|
||
```text
|
||
Laravel 13
|
||
PostgreSQL
|
||
React or Vue starter kit
|
||
Laravel queues
|
||
Separate PHP MQTT worker
|
||
Mosquitto
|
||
```
|
||
|
||
Laravel 13 has first-party modern frontend integration and remains actively supported. ([Laravel][1]) The downside is that persistent MQTT consumers and highly concurrent real-time connections are somewhat less natural in traditional PHP deployment models than in Node.js or .NET.
|
||
|
||
### FastAPI/Python
|
||
|
||
FastAPI is excellent for data processing, GIS, ML, and quick APIs, but I would not select it merely because Python is popular. Its built-in background tasks are intended to run after HTTP responses, rather than serve as a complete durable job-processing architecture. ([FastAPI][4]) It makes sense if significant Python-specific processing will occur.
|
||
|
||
## My final choice for your project
|
||
|
||
Given your embedded devices, MQTT requirements, .NET MAUI exposure, and expectation that this may become a serious customer-facing platform:
|
||
|
||
```text
|
||
Backend: ASP.NET Core
|
||
Database: PostgreSQL + Entity Framework Core
|
||
MQTT: Mosquitto + MQTTnet worker service
|
||
Frontend: React + TypeScript
|
||
Realtime: SignalR
|
||
Local setup: Docker Compose
|
||
Hosting: A single Linux VM initially
|
||
```
|
||
|
||
I would build it as a **modular monolith**, with the MQTT ingestion worker as a separate process. That gives you modern tooling and reliable long-running MQTT handling without adding unnecessary infrastructure. NestJS would be my second choice, particularly if you want TypeScript across the entire web stack.
|
||
|
||
[1]: https://laravel.com/docs/13.x/releases?utm_source=chatgpt.com "Release Notes | Laravel 13.x - The clean stack for Artisans ..."
|
||
[2]: https://docs.nestjs.com/microservices/basics?utm_source=chatgpt.com "Microservices | NestJS - A progressive Node.js framework"
|
||
[3]: https://dotnet.github.io/MQTTnet/?utm_source=chatgpt.com "MQTTnet"
|
||
[4]: https://fastapi.tiangolo.com/tutorial/background-tasks/?utm_source=chatgpt.com "Background Tasks"
|