Monaf Horany
All work

002

2025 — 2026

OUKITEL B2B Suite

Four Flutter applications and a web client sharing one NestJS backend, covering business sales, repair intake and returns.

Flutter apps
4
shared API
1
published
Play Store

Architecture

OUKITEL B2B suite architectureFour Flutter applications and a Next.js web client all call one NestJS API. The API owns PostgreSQL through Prisma, issues presigned URLs so devices upload files directly to S3 without proxying through the server, pushes background jobs to BullMQ over Redis, and sends push notifications through Firebase.CLIENTSAPIDATAdirect upload · presigned URLBusinessFlutterRepairFlutterProgrammeFlutterReturnsFlutterWeb clientNext.jsNestJS APIrole-scoped surfacesPostgreSQLPrismaRedisBullMQ queuesAWS S3presignedFirebasepush
Four Flutter applications and a Next.js web client all call one NestJS API. The API owns PostgreSQL through Prisma, issues presigned URLs so devices upload files directly to S3 without proxying through the server, pushes background jobs to BullMQ over Redis, and sends push notifications through Firebase.
  • Client
  • Service
  • Data store
  • External

The problem

OUKITEL's regional operation needed tooling for distinct roles that all touch the same inventory and customer records — a sales app, a repair-tracking app, a programme app and a returns workflow.

Four separate audiences, four separate apps, one set of business records. Building four backends would have guaranteed four divergent definitions of a device, a customer and a repair. Building one app with four modes would have shipped every role a cluttered interface for jobs it never does.

The approach

One NestJS API with role-scoped surfaces, and four thin Flutter clients that each present only what their role needs. Files move through presigned S3 URLs rather than through the API process, so uploading repair photographs never occupies a request worker. Slow work — notifications, exports, report generation — goes onto BullMQ queues.

Decisions that mattered

  1. 01

    One API, four clients

    A single NestJS service owns the schema and exposes role-scoped surfaces, documented with Swagger. Each Flutter app is a thin client over it. Adding a fifth role means adding a surface, not a backend.

  2. 02

    Presigned uploads

    Repair photographs go straight from device to S3 through presigned URLs. The API issues the credential and records the result; it never proxies the bytes. Upload throughput stops competing with request handling for the same worker pool.

  3. 03

    Queues for anything slow

    Notification fan-out, spreadsheet exports and report generation run on BullMQ over Redis. A request that triggers a thousand notifications returns immediately and the work drains in the background.

  4. 04

    Localisation in the API, not the clients

    `nestjs-i18n` resolves language server-side so four clients never carry four drifting copies of the same message catalogue.

Hard problems

  1. 01

    Four apps, one release discipline

    Shared Dart models and a common HTTP layer are lifted out of the individual apps, so a contract change lands once rather than four times. The alternative — copy-pasting a model across four repositories — fails silently the first time someone forgets one.

  2. 02

    Field conditions

    Repair intake happens in workshops with unreliable connectivity. Secure local storage holds credentials and in-progress records so a dropped connection loses nothing, and writes reconcile when the device comes back.

  3. 03

    Authorisation that survives a decompiled client

    Role gating in the app controls what a user sees; it is not what stops them. Every endpoint enforces its own guard server-side, on the assumption that any client can be modified.

My role

Sole engineer across the backend, all four Flutter applications and the Next.js client.

Status

The business application is published on Google Play. The suite is in production use.

Stack

Mobile
FlutterDartFirebase
Backend
NestJSPrismaPostgreSQLRedisBullMQSwagger
Storage
AWS S3Presigned URLsMinIO
Frontend
Next.jsTypeScript