Building an HTTP client with Zipper
Turn a Typescript function into a custom HTTP client web app using Zipper.
By
Antoniel Magalhães
When you're building APIs or software that interacts with APIs, you often land up needing to send a bunch of HTTP requests to confirm that everything is working the way you want. A lot of us rely on cURL in the terminal or tools like Postman and Paw on our desktops to do this.
Today we'll show you how to build the simplest possible HTTP client using a few Typescript functions, the fetch API, Zipper's autogenerated UI.
The requirements
For the majority of our use cases, we need need an HTTP client that:
- Supports all request methods
- Allows different request body content types
- Lets you customize the headers
- Supports cURL commands
Usage
The Simple HTTP Client provides two interfaces for the user to interact with the tool: manual request construction and curl command parsing. Developers have the ability to detail every part of their HTTP request. This includes specifying the request method (GET, POST, PUT, DELETE, etc.) and finely tuning the URL, headers, and body data. It's an interactive mode where each parameter can be adjusted to test various scenarios.
main.ts
facilitates manual HTTP request construction, allowing the specification of methods, URLs, headers, and body content.
curl.ts
provides an interface that requires only a curl command. The application
parses this command to build and execute the HTTP request.
Every request is converted into a URL with all the necessary information. This means you can save it, share it, and revisit it anytime you need to make the same request again. It's perfect for testing and debugging because you can make changes and see the results immediately. This is great because developers can share a link to the exact state of their HTTP request with team members. The recipient can see and execute the request as if they were the original author, simplifying troubleshooting and collective development efforts.
Take this example URL: https://simple-http-client.zipper.run/run/main.ts?method=GET&url=https://pokeapi.co/api/v2/pokemon
It encapsulates a GET request to the Pokémon API. Anyone with the link can execute the same request and see the results in
real-time, which is not only efficient for testing but also practical for demonstrations.
How it works
Making HTTP Requests using Fetch
Zipper applets are composed of one or more Typescript files. The entrypoint for every Zipper applet is main.ts
.
Exporting a handler
function from any Typescript file in the applet turns that file into a route. The inputs for the handler
function are converted into a web form.
For the Simple HTTP Client that we created, the handler
functions looks as follows:
export enum Methods { GET = 'GET', // All HTTP Methods } export enum BodyTypes { JSON = 'JSON', // } type Inputs = { method: Methods; url: string; headers?: any; bodyType: BodyTypes; body?: string; }; export async function handler(inputs: Inputs) { //... }
This automatically renders a web UI like this:
Once the inputs have been collected, they're passed into your handler function which then makes the appropriate fetch call.
export async function handler({ method, url, bodyType, ...inputs }: Inputs) { const response = await fetch(url, { method: method, headers: { // matchContentType gets the correct header value based on the bodyType input 'Content-Type': matchContentType(bodyType), ...inputs.headers, }, body: body, }); return response; }
Just returning the response object doesn't give us everything we need. We need to figure out the content type of the response so that we can correctly stream and read the body of the response.
export async function handler({ method, url, bodyType, ...inputs }: Inputs) { const start = Date.now(); const response = await fetch(url, { method: method, headers: { 'Content-Type': matchContentType(bodyType), ...inputs.headers, }, body: body, }); const end = Date.now(); const contentType = response.headers.get('content-type'); let decodedResponse: any = null; if (contentType && contentType.includes('application/json')) { decodedResponse = await response.json(); } if (contentType && contentType.includes('text/html')) { decodedResponse = await response.text(); } const headersResult = Object.fromEntries(response.headers.entries()); return { response: decodedResponse, status: response.status, duration: `${end - start}ms`, ...(isEmptyObject(headersResult) ? {} : { headers: headersResult }), }; }
Parsing and running cURL commands
To parse cURL commands, we added a second route to our applet: curl.ts
. The handler
function of this route only has a single input: the curl command itself.
Once we've got the cURL command, we use the parse-curl
npm package to get the information we need to construct a fetch call.
export async function handler({ curl }: { curl: string }) { const fetchOptionsFromCurl = parseCurl(curl) || {}; const start = Date.now(); const response = await fetch(fetchOptionsFromCurl.url, { ...fetchOptionsFromCurl, }); const end = Date.now(); const contentType = response.headers.get('content-type'); let decodedResponse: any = null; if (contentType && contentType.includes('application/json')) { decodedResponse = await response.json(); } if (contentType && contentType.includes('text/html')) { decodedResponse = await response.text(); } const headersResult = Object.fromEntries(response.headers.entries()); return { response: decodedResponse, status: response.status, duration: `${end - start}ms`, ...(isEmptyObject(headersResult) ? {} : { headers: headersResult }), }; }
Make it your own
Like any public Zipper applet, you can can fork simple-http-client
to make it your own. Some ideas of features you could add are:
- Authorization configuration
- GraphQL support
- Additional configuration such as whether to follow redirects or not
Dive deeper into Zipper's capabilities and tailor them to fit your development needs by visiting our website. For any inquiries or to share your valuable feedback, contact our responsive team at hello@zipper.works. Let's collaborate to refine web development with Zipper's streamlined solutions.