What a legacy system REST wrapper actually is

A legacy system REST wrapper is a small, purpose-built HTTP service that sits in front of an older application's data store and presents a documented set of resources to everything else. It is deployed somewhere with network access to that data store, it authenticates its callers, and it exposes endpoints such as /customers, /stock-levels or /sales-orders with a fixed request and response shape. Behind the endpoint it may run a parameterised query, call a stored procedure, or invoke an interface the vendor already supplies but which is awkward for modern consumers to use directly. The point of the wrapper is not novelty. It is to put a stable, versioned contract between one brittle thing and several other things that need data from it.

It is worth being precise about what such a wrapper is not. It is not a replacement for the legacy application, nor an attempt to reimplement its business logic. It is not a reporting warehouse, although it may feed one. It is not a complete API surface over every table in the database. A well-scoped wrapper covers only the resources that named consumers have been shown to need, which in most engagements is somewhere between four and twelve endpoints. Anything beyond that is speculative work that will be maintained forever and used by nobody.

The wrapper competes with a small number of alternatives, and the decision should be made against them explicitly. Those alternatives are: a scheduled file export to SFTP; direct database access from the consuming system via ODBC or a linked server; the vendor's own interface, where one exists; and replacing the legacy system altogether. Each of these has a genuine case. A file drop is cheap and auditable but latency-bound and difficult to make transactional. Direct database access is fast to build and dangerous to maintain, because every consumer then carries a copy of the schema knowledge. Replacement is sometimes correct but rarely available on the timescale the problem demands.

The conditions that make a wrapper the right choice

The strongest case for a wrapper arises when several conditions hold at once. Not all of them are necessary, but if only one applies the decision usually deserves a second look. The conditions below are the ones that come up repeatedly in discovery, and they are worth testing against your own situation before any code is scoped.

In practice, the multiple-consumer condition does the most work. A single point-to-point interface between an on-premise ERP and one cloud application can often be built as a direct connection without much regret. The moment a second and third consumer appear, each one either duplicates the schema knowledge or depends on the first integration in ways nobody documented. A wrapper turns that into one place where table names, join conditions and the meaning of a status code of 4 are recorded and tested. When the legacy schema changes, one component needs amending rather than four.

The second condition that carries real weight is the decommission path. Organisations running Microsoft Dynamics NAV or an older Sage 200 installation frequently know they will move to something else within two or three years, but cannot pause trading integrations in the meantime. A wrapper provides a seam: consumers are written against the wrapper's contract, and when the underlying system is replaced, the wrapper's implementation is rewritten while the contract stays still. That is a modest amount of extra work now in exchange for not rewriting every downstream integration later.

  • The data model is stable — The legacy schema has not changed materially in several years and the vendor's release notes do not indicate it will. Stability in the tables you depend on matters more than stability in the product as a whole.
  • Reads dominate the workload — You need stock figures, customer records, price lists or order status out of the system far more often than you need to write into it. Read-heavy requirements are where a wrapper is safest and quickest to prove.
  • A vendor interface exists but is impractical — SOAP endpoints that require a Windows domain account, on-premise-only OData feeds, or interfaces that cannot express the filter you need. The wrapper normalises this rather than replacing it.
  • There is more than one consumer — Two or more systems, or two or more teams, need the same data. A shared contract is cheaper to maintain than several private ones.
  • The system is on a known decommission path — The wrapper becomes the abstraction seam that lets the underlying platform be replaced without rewriting every integration attached to it.
  • Network topology forces an intermediary anyway — An on-premise SQL Server that cloud platforms such as Shopify or Xero cannot reach requires something in between. If you are building that component regardless, give it a proper contract.

When a wrapper is the wrong answer

The clearest contraindication is writing into financial or transactional tables. Accounting and ERP systems keep a great deal of behaviour outside the table structure: posting routines, nominal analysis, VAT determination, document numbering, audit trail entries and period controls. Inserting a row into a sales ledger table in Sage 200, or into a document table in Microsoft Dynamics NAV, can produce data that inspects correctly in the application and is nevertheless wrong in the ledger. The failure surfaces weeks later at reconciliation, in a period that has already been reported. If a write must happen, it should go through the vendor's supported posting mechanism, and if none exists, the honest answer is often a staging table the application itself consumes, or a controlled manual step.

Support and licensing constraints are the second reason to stop. Several vendors treat direct database writes as grounds for withdrawing support, and some treat direct reads the same way in specific modules. This needs establishing in writing during discovery, not assumed. Related to this is upgrade exposure: if the legacy platform is on an active upgrade path with schema changes in each release, a wrapper built against undocumented tables acquires a maintenance obligation at every upgrade. That obligation may still be acceptable, but it should be priced and assigned to a named owner rather than discovered later.

Production load is a third consideration, and one that operations managers tend to raise before anyone else does. A wrapper that runs unbounded queries against the live transactional database at the same time as warehouse pickers are confirming despatches will eventually cause blocking. Where the legacy platform supports a read replica, a log-shipped secondary or a nightly restored copy, read endpoints should be pointed at it and the resulting data latency stated plainly in the contract. Where it does not, query cost limits, timeouts and off-peak scheduling become part of the design rather than an afterthought.

Finally, a wrapper is the wrong answer when a supported and sufficient interface already exists. If a warehouse platform such as Indigo WMS already publishes the movements you need in a documented format, wrapping the database behind it adds a component, a deployment and a failure mode for no benefit. The test is whether the existing interface can express the queries and volumes required within its rate and latency limits. If it can, use it and record the decision.

Designing a legacy system REST wrapper that survives production

Read endpoints should be designed for incremental consumption from the outset. Full-table pulls are convenient during the build and unaffordable in production once the customer table passes a few hundred thousand rows. Pagination should use a stable cursor — typically a composite of a modified timestamp and a primary key — rather than an offset, because offsets shift under concurrent writes and silently skip records. If the legacy database supports change tracking or change data capture, use it. If it does not, a reliable last-modified column is the next best thing, and if that does not exist either, the wrapper should say so explicitly and the consuming integration should be designed around periodic full reconciliation rather than pretending to be event-driven.

Write endpoints, where they are justified at all, should be narrow and deliberately unfriendly to casual use. Each write should accept an idempotency key supplied by the caller, store it with the resulting internal identifier, and return the original result on replay rather than creating a second record. Where the legacy system provides a stored procedure that performs the business logic, call that procedure rather than assembling the insert yourself. Where the write must land in a staging table for the application to pick up, expose the downstream status through the same resource so that callers can determine whether their submission was accepted, rejected or is still pending.

The contract itself deserves the same attention as the code. Publish an OpenAPI document, version the resources in the path, and treat any change to a field's meaning as a breaking change even when the type is unchanged. Authentication should be appropriate to the deployment: mutual TLS or API keys combined with IP allowlisting is usually sufficient for a service that only ever talks to known systems. Every request should carry a correlation identifier that is written to the wrapper's log and, where possible, into the legacy system's own audit fields, so that a disputed record can be traced end to end without guesswork.

Testing a wrapper is mostly about the data rather than the code. Acceptance testing should run against a restored copy of the production database, using a set of records chosen during discovery to cover the awkward cases: the customer with a null country code, the order with a negative line, the product whose description contains characters the legacy application never expected. Once live, run the wrapper alongside the existing manual process for an agreed period and compare outputs daily. That comparison is the only reliable way to find the assumptions nobody articulated during mapping.

Scoping, ownership and the handover that matters

Discovery on a wrapper project is field-level work, and the output should be a mapping document rather than a diagram. For each resource, the mapping records the source tables and joins, the transformation applied, the authoritative system for that field, and the behaviour when the value is absent. It also records volumes: how many customers, how many orders per day at peak, how many stock movements per hour. Those figures determine whether a read replica is necessary and whether the consuming platform's own limits will become the constraint. Only once that document exists can a fixed scope and estimate be agreed with any confidence, which is why we do not quote build work before it is complete.

Ownership needs settling before go-live, not after. A wrapper is a running service: it has a host, a certificate, a database account, a log destination and a patching schedule. The database account should hold the minimum rights the endpoints require, granted per object rather than per database, so that a defect cannot cause damage beyond its intended surface. Monitoring should alert a named person when the wrapper cannot reach the legacy database or when error rates exceed an agreed threshold, and the runbook should state what that person does next. An unmonitored integration component is a silent failure waiting for month end.

Handover should include the source code, the OpenAPI contract, the mapping document, the deployment instructions and a schema dependency register listing every table, view and stored procedure the wrapper touches. That register is what makes the next legacy platform upgrade a checkable exercise rather than a gamble: the upgrade team reads the list, confirms which objects have changed, and the wrapper is amended accordingly. It is also what makes eventual decommissioning orderly. When the legacy system is finally replaced by a modern platform, the consumers keep calling the same endpoints while the implementation behind them is rewritten, and the integration estate does not have to be rebuilt alongside the ERP migration.

Key points

  • A wrapper earns its place when reads dominate, the schema is stable and more than one consumer needs the same data; with a single consumer and a short remaining lifespan, a file interface is often sufficient.
  • Direct writes into ledger or document tables bypass posting logic, VAT determination and audit trails; use the vendor's supported mechanism, a staging table the application consumes, or accept a controlled manual step.
  • Treat the wrapper as a running service with a versioned contract, least-privilege database credentials, correlation logging and a named owner, and keep a schema dependency register so future upgrades can be checked rather than guessed at.