DEV Community

Cover image for Replacing UnityWebRequest boilerplate with a visual API workflow
Andrew
Andrew

Posted on

Replacing UnityWebRequest boilerplate with a visual API workflow

Every project that talks to a backend ends up with the same code.

You build a request, set the method and headers, attach a JSON body, wait for it, check for errors, then parse the response and dig out the two or three values you actually need. Then you copy that block for the next endpoint.

The annoying part isn't any single request. It's that the shape is identical every time, and only the URL and the path into the JSON change. And when you need to chain calls (log in, get a token, use the token for the next request), the boilerplate multiplies.

I got tired of writing it, so I built a tool that replaces the request-building part with a node graph. This post is about the model behind it, and where it does and doesn't fit.

What the boilerplate actually looks like


using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.Networking;

    public async Task<string> GetDisplayName(string email, string password)
    {
        JObject body = new JObject
        {
            ["email"] = email,
            ["password"] = password
        };

        using UnityWebRequest request = new UnityWebRequest("https://api.example.com/auth/login", "POST");
        request.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(body.ToString()));
        request.downloadHandler = new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");

        await request.SendWebRequest();

        if (request.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError(request.error);

            return null;
        }

        JObject response = JObject.Parse(request.downloadHandler.text);

        return response["user"]?["profile"]?["display_name"]?.ToString();
    }
Enter fullscreen mode Exit fullscreen mode

None of that is wrong, but it is long and it has several places where it's easy to make a mistake: the JSON path, the header names, and the content type. Multiply it by five endpoints and you have a file nobody wants to touch.

The idea: a request chain as a graph

Every request has the same four parts: an entry point, an endpoint configuration, a response, and the values you want out of the response.

In a node graph, those become four node types:

  • Workflow node: The entry point. One per operation (Login, GetProfile, SubmitScore). Each gets an immutable integer ID for runtime lookup.
  • HTTP Request node: Method, URL, query parameters, headers, auth, body.
  • Parse node: Holds the JSON response.
  • Extract Properties node: Maps selected JSON paths to aliases you choose.

The part that actually saves time is the property extraction. Instead of typing data.user.profile.first_name and finding out it's wrong at runtime, the dropdown is populated from the actual response. You pick user > profile > display_name, give it the alias DisplayName, and the path is validated against real data while you're still in the editor.

Dynamic values

Hardcoded requests are easy enough, but the problem is with values you don't have at design time.

There are two mechanisms:

Request variables: Placeholders in the form {var:KEYNAME} that you assign at runtime, either from code or from Visual Scripting nodes. Login credentials are the obvious case: the user types them into a UI field, you assign them to the variables, then run the workflow.

Input paths: Values that come from a previous response in the same chain. Log in, extract access_token, then reference that path in the Authorization header of the next request.

APICanvasGraphRuntime graphRuntime = APICanvasGraphRuntime.Active;
        WorkflowRuntime workflowRuntime = graphRuntime.GetWorkflow(loginWorkflowId);

        workflowRuntime.SetRequestVariable("EMAIL", emailField.text);
        workflowRuntime.SetRequestVariable("PASSWORD", passwordField.text);

        if (await workflowRuntime.ExecuteWorkflow())
        {
            string value = workflowRuntime.GetProperty("AuthToken")?.ExtractedValue;
        }
Enter fullscreen mode Exit fullscreen mode

Editor and runtime are separate

The graph is a design-time interface. It's built on GraphView and UIElements, and it serializes to ScriptableObject assets. None of that ships in a build.

At runtime, the asset is read and executed with UnityWebRequest. The Editor assemblies aren't referenced at all, so there's no risk of a build pulling in UIElements or GraphView.

I have taken extreme caution to ensure that editor-only dependencies are completely separated into their own assemblies to ensure they never interfere with builds.

Where this fits, and where it doesn't

It fits when:

  • You have several endpoints, not one.
  • Auth is involved, especially token flows that require chaining.
  • Someone on the team isn't a programmer and needs to adjust endpoints without touching C#.
  • You keep rebuilding the same request logic across projects.

It doesn't fit when:

  • You have a single hardcoded GET call. A UnityWebRequest and JsonUtility.FromJson is fine.
  • You need streaming or websockets. This is request/response only (for now).
  • You need a generated data model per endpoint right now. Values come out as strings with their JToken type, and you parse them yourself (also for now).

The request body is currently raw JSON, which is a real limitation if you're building large payloads. A visual JSON builder is planned.

Closing

The tool is called API Canvas. It's on the Unity Asset Store, with documentation and sample scenes, including a login/registration flow. If you've been writing the same request block in every project, it's worth a look.

Top comments (0)