Yogi Prasetya monogram logo
Skill Graph
Loading theme switcher
Projects

Requflow

Test APIs with or without OpenAPI docs

Cover image for Requflow

Problem

Most API tools assume you already have OpenAPI documentation.

If your API has a spec, you use Swagger UI or a generated client. If it doesn't, you open Postman and type every endpoint in by hand. The tooling is built for one case or the other, but a lot of real APIs sit in the middle: partially documented, documented in a wiki page, or documented only in the heads of the people who built them.

Requflow is an API explorer that handles both cases in the same workspace.

Role

Solo project. I framed the problem, designed the interaction, and built the application. It is frontend only. No backend is a deliberate constraint.

Approach

Two modes, one data model

The central idea is that importing a spec and adding an endpoint by hand should produce the same kind of object. Both paths write to one Endpoint type, distinguished by a source_type field of spec or manual.

That means a workspace can mix them. Import a partial spec, hand-add the three endpoints it doesn't cover, and the sidebar, request builder, and export all treat the result identically. A separate store and separate components per mode would have duplicated the explorer, the request builder, and the export path.

Nothing leaves the browser

Requflow has no server. Workspaces, imported specs, and endpoint definitions live in localStorage behind a persisted Zustand store. Requests go straight from the browser to the target API using fetch.

For an API client this matters more than usual, because the tool is holding your API keys and your request bodies. With no backend there is no third party in the path and nothing to deploy. The cost is CORS: a request to an API that doesn't return Access-Control-Allow-Origin fails in the browser. The fix sits on the user's side. Configure CORS on the API, or use a browser extension while testing locally.

The spec is stored whole

An imported spec is kept as its original document rather than being exploded into endpoint rows.

Keeping the source means re-importing is a single replace, validation can re-run at any point, and details the UI doesn't surface yet, such as servers and security schemes, are still there when they're needed. The tradeoff is storage shape: one large value instead of many small ones. In practice specs sit well under a megabyte, which localStorage handles comfortably.

Scope that stops

Several obvious features are deliberately absent: request history and replay, a code-snippet generator, authentication presets, spec diffing, and a CORS proxy. Each was cut because it wasn't needed to prove the central idea, which is that dual-mode works in a single workspace.

The README records these as decisions with reasons rather than as a backlog of unfinished work: not built yet and not built on purpose are different claims.

Technical decisions

Why Zustand over React context?

Workspace state is read by the sidebar, the request builder, the response viewer, and the import flow. Threading that through context would have meant a provider tree plus manual memoization to stop the response viewer from re-rendering on every keystroke in the URL field.

Zustand is about 1KB, keeps the store outside React, and its persist middleware handles the localStorage serialization I would otherwise have written by hand. Selecting slices of state per component keeps re-renders local.

Why Base UI over Radix?

Base UI's types are more precise at the component boundary and the package is smaller. The primitives are unstyled either way and the styling comes from Tailwind, so changing the headless layer cost nothing in design terms.

Why no Axios?

fetch covers what the request executor actually does: build a URL from path, query, and header parameters, execute, and parse the response. Axios would have added roughly 13KB for interceptors and progress events this app doesn't use.

Why Next.js if there's no server?

Next.js 16 with the App Router gave me file-based routing, a layout system that handles the shell once, and React Server Components for the static parts. The build output is just files, so it deploys to any static host. The framework is doing UI work here, not backend work.

Outcome

  • Live at requflow.vercel.app
  • Import an OpenAPI spec or build an endpoint by hand, in the same workspace
  • No backend, no account, and no request data leaving the browser
  • Covered by tests on the Node.js test runner: workspace store behaviour (create, rename, delete, switch), import/export round-trips, and the URL-building and header-merging helpers
Next.jsReactTypeScriptZustand