The refactor that cut REST Express generated code by 42%
Generated code has a weight. If we ship a megabyte of C# into every customer project, we are a problem. A walkthrough of the single afternoon that made REST Express almost half as heavy.

Generated code has weight
REST Express emits one C# method per Postman request, plus the request / response types. For a medium collection — say, 40 endpoints, 25 shared models — the generated output used to be about 92 KB of C#. Not catastrophic, but not free either. Spread across 1,000 installs, that's 90 MB of generated code living in production projects.
We felt uneasy about it. A "tiny plugin" that ends up sprinkling megabytes of code into customer repos isn't tiny. Last quarter we set aside an afternoon to see how much we could cut. The answer was 42%.
Here's the walkthrough.
The code was repeating the framework
Most of our generated methods looked like this:
public static async Task<Response<User>> GetUserAsync(string id) {
var req = new RestRequest("/users/{id}");
req.AddUrlSegment("id", id);
req.Method = HttpMethod.Get;
req.AddHeader("Content-Type", "application/json");
var response = await Client.ExecuteAsync<User>(req);
if (response.IsSuccessful) return Response<User>.Ok(response.Data);
return Response<User>.Error(response.ErrorMessage);
}
Every method had the same shape. The variation was in the verb, the path, and the parameter list. Everything else — success handling, error wrapping, content-type header — was boilerplate repeated 40 times.
The refactor
We moved the boilerplate into a shared Execute<T> helper and let the per-endpoint method reduce to an expression body:
public static Task<Response<User>> GetUserAsync(string id) =>
Execute<User>(HttpMethod.Get, "/users/{id}", urlSegments: new { id });
The Execute<T> helper gets generated once, at the top of the file, regardless of how many endpoints there are. It handles URL building, header attachment, success/error wrapping, and deserialization in one place.
The generated code for the same 40-endpoint collection dropped to 53 KB. 42% smaller.
The things that didn't change
- The public API. Customers didn't have to re-generate their code or update their call sites.
UsersApi.GetUserAsync("123")still compiles exactly the same way. - The runtime behavior. Same HTTP calls, same parse paths, same error types. This was pure code-shape refactoring, not a behavior change.
- The per-call allocation profile. The anonymous object for
urlSegmentsis a slight bump — one extra heap allocation per request. In a game that makes 10 API calls per frame, that matters. For a game that makes a request every few seconds during a login flow, it does not. We picked the trade-off deliberately.
The thing we almost missed
The first version of the refactor made the generated methods depend on a shared RestExpressRuntime.dll. That would have moved the code from "in your project" to "in our plugin" — smaller generated output, but a new runtime dependency.
We reverted that. The whole point of REST Express is that the customer owns the generated code — it lives in their repo, they can read it, they can modify it. A runtime DLL would have broken that contract. The final refactor keeps the code inlined, just compressed. The extracted helper lives inside the generated file, generated exactly once per file.
The generalization
If you're shipping a code generator, run this exercise at least once a year. Open one of your customers' generated files. Ask: where is this code repeating the framework? The repetitions are almost always just boilerplate waiting to be lifted.
Code generation feels cheap when you're writing the generator. But the output lives in someone's repo, gets imported in their IDE, gets stepped through in their debugger. Every byte of it is your name on the page. Smaller is better, almost always.
