Portrait of Patrice GADEGBEPatrice GADEGBE

Sanu

Sanu is a management and point-of-sale system for retail in West Africa, which I founded and whose development I have largely carried out since January 2025.

Role
Founder and lead developer, in charge of product, architecture and development, with two other developers
Period
From January 2025 to today
Status
In development. In development, deployed to staging
Technologies
Java 21, Spring Boot 4, Spring Modulith, PostgreSQL, React, Flutter
Repositories
Seven
Overall architecture of SanuThree interfaces, the cashier application in Flutter, the back office and the console in React, call an nginx proxy over HTTPS. The proxy forwards ordinary calls through /api and the live channel through /api/ws to a Spring Boot API split into modules, which relies on a PostgreSQL database.CashierFlutter, registered terminalBack officeReact, 72 pagesConsoleReact, administrationnginxreverse proxyAPISpring Boot 4, Java 21, Spring Modulithregistersalesorderpurchasingstocktreasuryaccessand twelve other modules, whose boundariesare checked on every test runPostgreSQLFlyway migrationsHTTPS/api/api/wsWebSocket
Figure 1. Overall architecture of Sanu in September 2026. The dashed line is the live channel, which runs in both directions.

Context

A first version of Sanu, made of a Django API and a Flutter application, was developed from January 2025. The current version, started in September 2026, separates an API written in Java, a back office and an administration console in React, and a cashier application in Flutter.

It was preceded by a specification effort, which produced a user manual organised by profile and by module, published as a static site, and a navigable prototype of the screens. That prototype is the reference for the three interfaces, so that none of them decides alone what a screen looks like.

Sale screen of the prototype: on the left the drinks catalogue with its packagings and prices in CFA francs, on the right the current order, empty, and the payment methods.
Figure 2. Sale screen of the prototype, the reference for the cashier application. The interface is in French.
Sales and till dashboard: revenue of the day, sales per hour, takings by payment method, best-selling products and open tills.
Figure 3. Back-office dashboard in the prototype, with fictitious data. The interface is in French.

Problem

The main difficulty lies in the access model. One person may belong to several companies, each company composes its profiles from permission codes, and a cashier station shared by several sellers must allow a quick sign-in by PIN without opening that convenience to any device.

Three interfaces written in two languages also consume the same API, which requires the access rules to be stated in one place only; otherwise they would end up diverging from one interface to the next.

Answer

Access model

The server resolves permissions on every request, from the person’s memberships and the profile the company gave them. The PIN is accepted only from a terminal registered beforehand, and a person holds one session at a time. These decisions are recorded in an architecture document, which the three interfaces take as their contract.

The API is a single Spring Boot application, split into modules whose boundaries Spring Modulith checks on every test run; figure 1 shows the general layout.

Live channel

The live channel goes through a WebSocket endpoint, /api/ws, which needs a location of its own in the nginx configuration. Without the upgrade headers passed through, the proxy answers 400 and no client ever connects; with the default read timeout of sixty seconds, an idle channel is cut every minute. The location is declared before the one for /api, so that these headers are not set on ordinary calls.

location /api/ws {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600s;
}
Listing 1. The nginx location reserved for the live channel.

Method

The new version is written for the most part with an assistant, Claude Code, which I drive by roles: an architect settles the structure, a developer writes inside those rules, a tester gives a verdict and a release owner ships. Each role hands the next a block of sourced facts, and a piece of work counts as finished only once the verification command has passed. Across the four code repositories, most commits carry the assistant’s co-author line. These roles are described in the study of the plugin repository.

Verification

Each repository has a single verification command, run identically on the developer’s machine and in the pipeline, which chains the format check, the compilation and the tests. On the server side, the integration tests run against a real PostgreSQL database, started in a container.

The required coverage is 100% on the domain and data packages of the Flutter application. It was relaxed for the presentation packages, after one feature took more time in widget tests than in development.

Table 1. Repositories of the project on 1 October 2026.
RepositoryContentTechnologies
sanu-backend-javaAPISpring Boot, PostgreSQL
sanu-app-reactBack officeReact, Vite
sanu-apps-flutterCashier applicationFlutter
sanu-app-prototypePrototype of the screensJavaScript
sanu-erp-specificationManual and product backlogMkDocs
sanu-console-reactAdministration consoleReact, Vite
sanu-apps-portalPortal to the servicesJavaScript

Limits and outlook

The back office reaches seventy-two pages, most of which remain placeholder screens until the corresponding functional batches are done. The cashier application covers counter sales for now, and the applications intended for delivery and for customers have not been started.

The new version is moreover deployed to staging only, so the choices described here have not yet been tested by real use. This study will have to be revisited once the first points of sale are equipped, to record what real use has confirmed or contradicted.

Back to the other projects