Designing a modular ERP backend with FastAPI, Celery, and Postgres: the decisions I would defend again
Building an ERP is a humbling exercise. The domain has been worked on for forty years and most of what looks novel in a greenfield design is a corner that a real business has already paid for in pain. I spent the better part of a year leading Guru Enterprise System, a single tenant modular ERP for small and mid sized retailers. These are the architecture decisions I would defend again, written for engineers thinking about similar systems.
FastAPI over Django
Django ships with an admin, ORM, auth, and forms. For an ERP that ships its own admin UI in React, half of Django is dead weight. FastAPI gave us:
- Pydantic schemas that double as request validation and OpenAPI documentation.
- Native async for routes that fan out to Redis and Postgres.
- A small surface that does one thing well.
We kept SQLAlchemy for the ORM, Alembic for migrations, and treated FastAPI as a routing and validation layer. The ERP domain has hundreds of endpoints. The OpenAPI spec generated by FastAPI is the single source of truth that the React frontend reads at build time to generate typed clients.
When Celery earns its complexity
Celery is overkill for a side project. For an ERP it pays for itself in a week. The jobs that run on Celery in Guru:
- End of day inventory reconciliation, batched per branch.
- Bulk PDF generation for invoices and purchase orders.
- Email and SMS dispatch with retry on failure.
- Periodic snapshot of stock levels for reporting.
The reason Celery wins over a simple cron is the retry contract. When the email provider returns a 502, Celery requeues with exponential backoff. When a worker dies mid task, the broker hands the job to another worker. You get the operational properties that turn "we lost a job once" into "we have not lost a job in nine months."
Postgres does more than you think
Most teams reach for Elasticsearch the first time someone asks for fuzzy search. We did not. pg_trgm and a GIN index on the product name handled fuzzy product lookups across 200,000 SKUs with sub 100 ms response times.
Same story for full text on invoices. tsvector plus a stored generated column gave us serviceable search without an additional service to operate. The bar for adding infrastructure should be high. Postgres clears most of it.
Redis is the queue, the cache, and the lock
Redis 7 plays three roles in Guru:
- Celery broker.
- Cache for read heavy endpoints (product list, exchange rates, tax tables).
- Distributed lock for "only one branch can settle the day at a time" constraints.
One service, three jobs, simple to reason about. The temptation to add Kafka or a dedicated lock manager came and went. The trade was not worth it.
The single tenant decision
The hardest call was making this single tenant per deployment. We knew multi tenant would have been operationally cheaper. The reason we went single tenant was that the customers asked for it. A trading company running its own books does not want its data co located with a competitor's, no matter how good your RLS is. Sales conversations are easier when the answer to "where does my data live" is "in your own Postgres instance."
Docker as the boundary
Frontend, backend, Celery workers, Redis, and Postgres all run in containers coordinated by Docker Compose for dev and Kubernetes for production. The orchestration layer is the contract. Every service knows how to start, knows what it depends on, and exposes health checks. The result is that onboarding a new engineer to the ERP takes one afternoon, not one week.
What I would tell hiring managers
ERP work teaches you about the boring parts of distributed systems. Idempotency, retries, exactly once delivery, cross service invariants. Engineers who have done it ship infrastructure that does not fall over the first time a partner API is slow.
If your team is hiring for a remote backend role and the system has any business critical batch processing, ERP veterans are an underrated pool.
Md. Tausif Hossain leads engineering at DevTechGuru and ships SaaS independently as TechnicalBind. He is open to remote Senior and Staff roles. Reach him at tausif1337.dev.
Newsletter
Get new posts in your inbox.
Honest essays on engineering, leadership, and the things I’m figuring out. No spam, ever.