Challenge
Operations teams needed flexible querying for inventory and finance without multiplying brittle REST endpoints.
Solution
Delivered a GraphQL API layer on NestJS/Prisma, Redis caching for hot reads, and Xero connectivity for invoicing and reconciliation workflows.
DAT is an enterprise ERP backend for inventory, reporting, and financial operations. The people using it are not browsing a marketing site. They are running stock, invoices, and reconciliation under time pressure. The API had to answer uneven questions: this screen wants a product with stock levels and last supplier; that report wants a slice of financial history; this job wants to push an invoice into Xero and later pull payment state back.
REST would have worked if the UI was frozen. It was not. Every new operations view threatened another endpoint, another DTO, another N+1 of “we will add a query param.” I designed the backend as a software engineer around GraphQL on NestJS, Prisma on PostgreSQL, Redis for hot reads, and Xero for the accounting system of record the finance team already trusted.
The challenge
ERP frontends are chatty in a legitimate way. A warehouse screen and a finance screen look at overlapping entities with different depth. If every combination becomes a REST resource, the backend turns into a catalog of one-off payloads. If you refuse and force the client to stitch five calls, the UI becomes slow and inconsistent. GraphQL is not magic, but it is a better contract when the query shape is the product.
Finance integration raised the stakes. Invoicing and reconciliation cannot be “best effort fire and forget.” Xero has its own objects, rate limits, and failure modes. Duplicate invoices are worse than delayed invoices. The ERP needed an integration path that was idempotent, observable, and reversible enough for ops to trust it on real money movement.
- Inventory and reporting queries that change shape per screen
- Avoiding an explosion of brittle REST endpoints as the UI grew
- API latency on repeated operational reads during the workday
- Xero invoicing and reconciliation that must not double-post
Approach
NestJS gave us modules that map to ERP domains: inventory, catalog, invoicing, and reporting. Prisma kept PostgreSQL access typed and migration-friendly. GraphQL sat as the query layer for the operations clients so each screen could ask for a graph instead of a fixed blob. That does not remove backend design. You still define types, authorization, and what is expensive to resolve.
Redis cached hot reads — reference data, frequently opened inventory snapshots, and query results that are safe to reuse for a short window. Cache invalidation followed writes in those domains. ERP users will forgive a spinner less than they will forgive stale stock that looks authoritative. We cached what is read-heavy and invalidated on the mutations that matter.
Xero was treated as an external ledger, not a dump of our tables. The ERP remains the operational source for inventory and internal documents. When an invoice is ready, we create or update the corresponding Xero object, store the remote ids, and reconcile status back. Webhooks or scheduled pull — depending on the flow — update payment and void state. Every outbound call is keyed so retries do not create a second invoice.
Architecture
GraphQL resolvers load through Prisma with an eye on over-fetching. Nested inventory → warehouse → stock movements is useful until someone requests a report-sized collection with deep relations. We used pagination, explicit field selection, and batched loading so a flexible API did not become an accidental full-table join. Authorization is field- and type-aware: finance fields are not a bonus include on an inventory type.
PostgreSQL holds relational truth: products, stock, documents, and integration cursors. Redis sits beside it, not in front of writes. AWS hosted the NestJS services with the usual production basics — environment isolation, secrets out of the repo, and room to scale the API independently of workers that talk to Xero. Slow third-party I/O does not belong on the query path that a warehouse screen is waiting on.
The invoicing pipeline is a state machine: draft in ERP, validated, pushed to Xero, acknowledged, reconciled. Failures land in a place ops can see — not only in a log line. That operational honesty is what makes an integration feel like part of the ERP instead of a weekend script.
What we shipped
A GraphQL API that operations UIs could grow into, a caching layer that made repeated reads cheap, and a Xero path for invoicing and reconciliation that finance could actually use. The DAT project page is the short version. The backend work was the contract design: what the client may ask, what must be consistent, and what may be eventually consistent because Xero said so.
- NestJS modules for inventory, reporting, and financial workflows
- Prisma and PostgreSQL as the typed system of record
- GraphQL for flexible operations queries without endpoint sprawl
- Redis caching on hot reads with write-aware invalidation
- Xero connectivity with idempotent invoicing and reconciliation status
Results and what held up
Frontend round-trips dropped because screens asked for one graph instead of a handful of REST resources. Cached reads made the daily operational loop feel snappier. Invoicing no longer meant exporting a file and re-keying it in Xero. Reconciliation had a path back into the ERP instead of a side spreadsheet.
GraphQL paid off because we treated it as an API with a performance budget, not as a license to query anything. Xero paid off because we treated it as a ledger with identity and retries, not a POST in a controller. If you need a similar ERP or finance-integrated backend, I take that work on as a Node.js developer — start a conversation.
Outcomes at a glance
- ✓Flexible querying reduced frontend round-trips
- ✓Caching improved API responsiveness
- ✓Automated invoicing paths via Xero
- ✓Inventory and finance screens could fetch exactly the graph they needed
- ✓Reconciliation workflows stopped depending on manual CSV hopping
Technology
Want a similar outcome? Contact me or review the FAQ for hiring and process details.
View matching project page