Article series
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):
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:
- Create it on your own or
- 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!
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.
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:
- In Charles: enable recording
- On your client: trigger all requests, which you will need later
- 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.
Conclusion
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.