Skip to content

Allow Internal API Requests #2224

Open
Open
@vyconm

Description

@vyconm

openapi-fetch version

0.13.5

Description

SvelteKit allows the optimization and deduplication of internal API calls, when staying server sided, using the SvelteKit provided fetch:

It can make relative requests on the server (ordinarily, fetch requires a URL with an origin when used in a server context).
Internal requests (e.g. for +server.js routes) go directly to the handler function when running on the server, without the overhead of an HTTP call. (reference)

As the the client is validating the resulting request URL, trying to access a internal route with only /api as basepath causes a fatal TypeError:

Network error: TypeError: Failed to parse URL from /api/internalRoute
    at new Request (node:internal/deps/undici/undici:9580:19)
    at coreFetch (file:///Users/user/VSCode/Project/front/node_modules/openapi-fetch/dist/index.js:97:19)
    ... 5 lines matching cause stack trace ...
    at async eval (/Users/user/VSCode/Project/front/node_modules/@sveltejs/kit/src/runtime/server/page/index.js:155:13) {
  [cause]: TypeError: Invalid URL
      at new URL (node:internal/url:819:25)
      at new Request (node:internal/deps/undici/undici:9578:25)
      at coreFetch (file:///Users/user/VSCode/Project/front/node_modules/openapi-fetch/dist/index.js:97:19)
      at Object.GET (file:///Users/user/VSCode/Project/front/node_modules/openapi-fetch/dist/index.js:241:14)
      at getDetailedWorkspace (/Users/user/VSCode/Project/front/src/routes/(protected)/workspace/+page.server.ts:16:77)
      at load (/Users/user/VSCode/Project/front/src/routes/(protected)/workspace/+page.server.ts:49:24)
      at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
      at async load_server_data (/Users/user/VSCode/Project/front/node_modules/@sveltejs/kit/src/runtime/server/page/load_data.js:51:18)
      at async eval (/Users/user/VSCode/Project/front/node_modules/@sveltejs/kit/src/runtime/server/page/index.js:155:13) {
    code: 'ERR_INVALID_URL',
    input: '/api/internalRoute'
  }
}

Why would I want to do that though?

Using an internal api endpoint allows us to centrally handle auth and eliminate request differences between server and client, something crucial for Server-Side-Rendering(SSR) - our code needs to be able to run on both client and server identically.

In our case, the Actual API is a seperate private instance in our AWS Cluster, so we can only access it through our SvelteKit Instance anyways - this means our client requests need to hit the internal /api endpoint in our SvelteKit instance via +server.ts.

A) Auth
Workaround
We grab cookies every time we make a page load request on the server.

export const load: PageServerLoad = async ({ parent, fetch, cookies }) => {
      const { data, error, response } = await serverClient.GET(
        "/workspaces/{workspace_id}/",
        {
          params: { path: { workspace_id: workspace_id } },
          headers: {
            Authorization: `Bearer ${JSON.parse(cookies.get("user_session") ?? "").some_token}`, 
          },
          fetch, // we are already passing SvelteKits fetch here
        },
      );
...
}

Problem
1.) we cannot do that on the client side(aws cluster only works internally/privately from SvelteKit instance), forcing us to use the internal /api route with a +server.ts anyways there.
2.) we cannot write a global middleware that adds the Authorization, as we cannot grab the cookies object outside of load functions / +server functions. A lot of duplicate code (room for errors to be made), and unneccessary.
3.) This also means it doesn't pass through SvelteKits Handle Hook (reference), which is SvelteKit's kind of middleware. This is where most people, including us, like to do a lot of auth handling, refreshing and verifying. This would also be very cumbersome to have to duplicate in code.

B) SSR compatible / hybrid-server-client functions, using the internal endpoint
Workaround
We use a base url when it exists (client side), and use an http://localhost prefix basePath when server side ( base doesnt exist there in svelteKit)

import { base } from "$app/paths";
import type { paths } from "../types.d";
import createClient from "openapi-fetch";

const client = createClient<paths>({
  baseUrl:
    (base && base !== "/" && base !== ""
      ? base
      : "http://locahost:5173") + "/api",
});

export default client;

Problem
a) While this does unify our approach, and behaves just as intended client-side, we create an entirely unncessary HTTP call to our own SvelteKit instance on the server, as sveltekit cannot deduplicate and optimize the call, recognizing it only as an external call. We call the API a lot, and this overhead quickly becomes significant.
b) Setting the internal server address (localhost:whatever) requires additional, unneccessary logic that is prone to errors and problems.

Would love some feedback on this, as maybe I am not seeing a nice solution with the current tool and feature-set or am simply finding a limit that is not implementable with sveltekit.. We would love to fully embrace this library, as the type safety between front and backend is simply pure gold, great job there guys!

Reproduction

SvelteKit =< 2, Svelte >= 3. Reproduction is listed above.

Expected result

Being able to call internal routes / not having TypeErrors.

Extra

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingopenapi-fetchRelevant to the openapi-fetch library

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions