Charles Proxy In Action: Mocking And Manipulating API Behavior With A Local Proxy Server – Part 3

In this five-part article series, you will learn how to manipulate your API behavior with a local proxy server. After setting the scene with an introduction and learning how to set up and configure Charles Proxy, we will now take a look at Charles Proxy in action. You will gain the skill to manipulate, fake, and replay API responses on your behalf.

In this article:

Mission 1: Testing the frontend against various API responses

The todo demo API returns a JSON array with three objects each with an id and title property. Now we want to test the frontend with a very long title.

We can do this via breakpoints that can be enabled through “Proxy” → “Breakpoint Settings”. There we can enable breakpoints per location. For the demo API, we want it on each response:

After setting up the breakpoints, we’re triggering a new request to the API. Charles halts the response:

With a click on “Edit Response” on the right side we’re able to change the response’s headers and body. In this case, we leave the headers as they are and only change the title of one object. Note: the Content-Length header will be updated automatically by Charles!

After clicking the “Execute” button, the frontend immediately receives the modified response (some frontends may throw a timeout error if it takes too long – please consider this):

This way, we can completely modify the response coming back from our API. We could even add new properties or change the structure of the response completely. Though, we have to modify every single response manually. Since that is not very practical, we are moving forward to our second mission.

Mission 2: Developing against an erroneous API implementation

We have already learned how to set breakpoints and change a single response on the fly. However, if we have an erroneous or outdated API implementation and need to continue developing the frontend, we need a way to receive the modified response each time automatically without interaction. Here, Charles’ mapping tools are coming into play.

These allow us to map a request’s response to a local file. Charles will replace the original response body with the contents of our local file. Exactly what we want!

There are two options to create this file:

  1. Create it on your own or
  2. save the original response to a file and edit its content with any text editor

Let’s take a look at the second option, that in a real-world application is more practical to use:

When done, we will edit it so that the JSON matches the requirements of the next development iteration. Since this is going to be a todo app, the todo items from the response need an additional property done. The result is:

    "id": "1",
    "title": "Todo 1",
    "done": false
}, {
    "id": "2",
    "title": "Todo 2",
    "done": false
}, {
    "id": "3",
    "title": "Todo 3",
    "done": true


Last but not least, the /api/todo endpoint needs to get mapped to the local file. When you have a recorded request, you can right-click and then select “Map Local…” at the bottom of the context menu. The following dialog will be pre-filled to match this request. Otherwise, you can do it manually by selecting “Tools” → “Map Local” from the menu bar.

Now let’s test the new response by triggering a new request from the frontend. Oops, something is still not working!

What happened is that Charles created the whole response on its own – forgetting to set the CORS headers. Our records will help us to evaluate the difference.

Response header from API:

					HTTP/1.1 200 OK
Server: Cowboy
X-Powered-By: Express
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Access-Control-Allow-Headers: X-Requested-With,Content-Type,Cache-Control,access_token
Content-Type: application/json
Content-Length: 88
Vary: Accept-Encoding
Date: Fri, 11 Oct 2019 08:54:09 GMT
Via: 1.1 vegur
Expires: 0
Cache-Control: no-cache
Connection: keep-alive

Response header from Charles:

					HTTP/1.1 200 OK
Content-Length: 156
Content-Type: application/json
X-Charles-Map-Local: /home/haggis/path/to/blog/proxy/demo-files/todo.json
Expires: 0
Cache-Control: no-cache
Connection: keep-alive

The Access-Control-Allow-* headers are important here. When we originally saved the response to a JSON file, Charles only saved the body and ignored the header. There does not seem to be a way to map a request to a file containing both header and body.

Fortunately, Charles also provides the Rewrite Tool. It allows us to alter a response or request automatically. We can choose between different types of actions, such as, adding/removing headers, modifying the body, or changing query parameters. In this case, however, it is enough to add headers:

When we send the request again, the CORS errors are gone, and the frontend receives the modified response from the local JSON file. Look at how the frontend can now handle the done property of todo items even though the API does not know anything about it!

Let’s continue to our next mission!

Mission 3: Developing against an incomplete API

The todo app is still missing the option to add new todo items. The current API provides only one endpoint: GET /api/todo to list all todo items.

Additionally, we want POST /api/todo to create a new todo item. It should return our new item. Unfortunately, this does not work with Charles. As both endpoints have the same location, we would need a possibility to distinguish requests by their method – which is a missing feature. We cannot give them different responses. In our case, the POST request also returns the list of all todo items. There is no way to work around this.

For the ake of this demonstration, let us assume our creation endpoint is POST /api/add/todo rather than POST /api/todo.

When we trigger this POST request from our client, we receive the expected result 400 (Bad Request) because there is no such route yet. So let us create a local JSON file containing the expected result:

    "id": "4",
    "title": "Todo 4",
    "done": false

Mapping the failed request to this file, and we are done. In an ideal world, we could add a rewrite rule on top to access the request’s body and pasting it into the response body. Unfortunately, that is also not possible. So we always get the same response back from Charles.

Great! We can test various responses and even fake endpoints. But what if we are going to travel and are expecting some offline time in advance?

Mission 4: Offline development against a remote API

We could solve this mission by mapping every single request to a local file. If we have a bigger API then our todo API, this task would become quite exhausting.

Charles provides an easy solution to this:

  1. In Charles: enable recording
  2. On your client: trigger all requests, which you will need later
  3. In Charles: save & map all those requests at once

Step 1. and 2. we’ve already done in this article. Let’s take a look at how batch saving a mapping of requests works.

As you can see, Charles organizes all requests in a hierarchical tree structure. Instead of right-clicking on a single request, we just have to right-click on a higher level. In this case, we save everything from “api” downwards.

When we get a warning that there are multiple requests for the same location and get asked whether we want to store all, only the first or only the last, this is probably because of the CORS preflight request. We should be fine by choosing to save only the last one.

As a result, we have got a complete folder with saved responses on our disc. To map them, again right-click on the appropriate level (here “api”), then choose “Map Local…”, and then select the directory with your recordings. If we have other active mappings for the same API, we recommend disabling those to prevent any unexpected behavior.

Switch off the network for a test and reload your frontend. Oops, now we have an SSL error!

By looking at Charles’ records, it becomes clear that Charles tried to open a tunnel to the remote server – even though there is a matching mapping. Unfortunately, there is no way to disable this behavior.

So no offline HTTPS API for the moment. However, if we are pointing our client to the insecure HTTP variant, it works. Voilà!

We can either do this in our clients. Or with Charles’ rewrite tool by creating a URL rewrite rule. If we choose the rewrite option, we also have to disable SSL decryption. Otherwise, Charles is trying to establish an SSL connection before the URL gets rewritten.


We have explored a couple of possibilities in this article, about how frontend developers can get more independent of the API they are working with. We have now gained the skill to manipulate, fake, and replay API responses on our behalf.

Unfortunately, we faced some issues on missions 3 and 4:

  • No different fake responses on the same route distinguished by HTTP methods.
  • No HTTPS fake responses when you are offline. Even if you have SSL decryption turned on and a matching local mapping.

These two issues may be a show stopper for a use case. However, Charles is still a useful tool for debugging and testing.

As you already might have seen, Charles even provides many more tools and possibilities than those, such as Throttling, DNS Spoofing, and more. Take your time to learn more about it, and you have an excellent tool that helps you debugging, testing, and developing your frontend as well as your backend.

In the upcoming article, we will explain how to setup and configure Fiddler.

More articles about API, Tools

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

EN Newsletter Anmeldung (#7)
Related Articles
One of the more pragmatic ways to get going on the current AI hype, and to get some value out of it, is by leveraging semantic search. This is, in itself, a relatively simple concept: You have a bunch of documents and want to find the correct one based on a given query. The semantic part now allows you to find the correct document based on the meaning of its contents, in contrast to simply finding words or parts of words in it like we usually do with lexical search. In our last projects, we gathered some experience with search bots, and with this article, I'd love to share our insights with you.
If you previously wanted to integrate view transitions into your Angular application, this was only possible in a very cumbersome way that needed a lot of detailed knowledge about Angular internals. Now, Angular 17 introduced a feature to integrate the View Transition API with the router. In this two-part series, we will look at how to leverage the feature for route transitions and how we could use it for single-page animations.
.NET 8 brings Native AOT to ASP.NET Core, but many frameworks and libraries rely on unbound reflection internally and thus cannot support this scenario yet. This is true for ORMs, too: EF Core and Dapper will only bring full support for Native AOT in later releases. In this post, we will implement a database access layer with Sessions using the Humble Object pattern to get a similar developer experience. We will use Npgsql as a plain ADO.NET provider targeting PostgreSQL.