-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
Describe the problem
It would increase ergonomics if the return type of fetch calls to custom endpoints (+server.js) was inferred inside load functions when using the provided fetch function.
I'm creating an application that needs to have a available APIs for integration purposes. I also have a database that can only be queried server side. Currently I can use my endpoints (+server.js) in my load functions using the provided fetch wrapper, and SvelteKit will automatically figure out if it should use HTTP to talk to the API, or just call the +server.js directly (which is awesome!):
// src/routes/api/books/+server.js
import { json } from "@sveltejs/kit";
export async function GET() {
/** @type {{ title: string; author: string; }[]} */
const books = await db.query(/* query */);
return json(books);
}
// src/routes/books/+page.js
import { error } from "@sveltejs/kit";
export async function load({ fetch }) {
/** @type {{ title: string; author: string; }[]} */
const books = fetch("/api/books")
.then(res => res.json())
.catch(() => {
throw error(500, "Failed to fetch books");
});
return { books };
}However, as you can see, I have to type out the result of the fetch to my endpoint (+server.js). This is obviously not a huge deal, but it can get quite tedious and error prone when the application size grows.
Describe the proposed solution
Ideally I would like the provided fetch wrapper inside load function (in +page[.server].js) to infer the type when querying internal endpoints.
If I have this +server.js file inside src/routes/api/books/:
import { json } from "@sveltejs/kit";
export async function GET() {
/** @type {{ title: string; author: string; }[]} */
const books = await db.query(/* query */);
return json(books);
}Then I would have these results in a +page[.server].js file:
export async function load({ fetch }) {
const books = await fetch("/api/books").then(res => res.json())
// ^ { title: string; author: string; }[]
}This raises some questions however. What will happen if you do fetch("/api/books").then(res => res.formData())? Will it error? And what if you want to do more fancy things inside your endpoint. For example provide JSON response only if some specific headers are set?
Alternatives considered
It is possible to turn all +page.js files that uses the API into +page.server.js files, and query the database directly. However, the downside of that is that it's quite easy for your APIs and your internal usage of the database to shift. By dogfooding the API, it's easier to maintain it over time.
Another approach (which is how we are doing it currently) is to use tRPC as an abstraction layer:
// src/routes/api/books/+server.js
import { json } from "@sveltejs/kit";
import { trpc } from "$lib/trpc";
export async function GET({ fetch }) {
const books = await trpc(fetch).books.all.query();
return json(books);
}
// src/routes/books/+page.js
import { error } from "@sveltejs/kit";
import { trpc } from "$lib/trpc";
export async function load({ fetch }) {
/** @type {{ title: string; author: string; }[]} */
const books = await trpc(fetch).books.all.query()
.catch(() => {
throw error(500, "Failed to fetch books");
});
return { books };
}
// src/wherever/you/keep/trpc/routers/books.js
export const booksRouter = t.router({
all: t.procedure.query(() => {
const books = await db.query(/* query */);
return books;
}),
});A downside to this approach is that you get yet another abstraction layer, and you have to set up tRPC.
Importance
would make my life easier
Additional Information
No response