Optimizing Code Imports with tRPC and SvelteKit

tRPC is a great tool for managing server-side procedures. However, it can be a bit tedious to import it in every `+page.server` file. In this article, I will show you how to optimize your code imports and avoid unexpected errors.

Updated
Avatar Chris Jayden
Chris Jayden

On This Page

    Love Svelte content? ❤️

    Get the tastiest bits of Svelte content delivered to your inbox. No spam, no fluffy content — just the good stuff.

    I have been using tRPC for quite a while now and I'm absoluting loing it. While using though, I realized that I kept repeating imports in my SvelteKit +page.server files.

    LIke any good developer, I decided to fix this problem. I created a utility function that would allow me to import tRPC only once and then use it in all my +page.server files.

    Another crucial thing this utility tackles is rethrowing the right SvelteKit error object. The original error object thrown by tRPC is not the right one for SvelteKit and results in an unexpected error.

    The utility function

    All you need to do is import your appRouter and createTRPCContext functions and pass them to the utility function. Keep in mind that these might be named differently in your project.

    The utility function will return a caller object that you can use to call your tRPC methods.

    src/lib/utils/trpcLoad.ts
    	import { appRouter } from '$lib/api/root';
    import { createTRPCContext } from '$lib/api/trpc';
    import { type RequestEvent, error } from '@sveltejs/kit';
    import { TRPCError } from '@trpc/server';
    import { getHTTPStatusCodeFromError } from '@trpc/server/http';
     
    export async function trpcLoad<
        Event extends RequestEvent<Partial<Record<string, string>>, string | null>,
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        Method extends (caller: ReturnType<typeof appRouter.createCaller>) => Promise<any>
    >(event: Event, method: Method): Promise<ReturnType<Method>> {
        try {
            const caller = appRouter.createCaller(await createTRPCContext(event));
            return await method(caller);
        } catch (e) {
            if (e instanceof TRPCError) {
                const httpCode = getHTTPStatusCodeFromError(e);
                throw error(httpCode, e.message);
            }
            throw error(500, 'Unknown error');
        }
    }
    

    To illustrate how this utility can be used, here is a sample usage in +page.server. This eliminates repeating imports while allowing the ability to load in parallel.

    Before

    src/routes/user/[username]/+page.server.ts
    	import { appRouter } from '$lib/api/root';
    import { createTRPCContext } from '$lib/api/trpc';
    import type { PageServerLoad } from './$types';
     
    export const load = (async (e) => {
        return {
            user: appRouter.createCaller(await createTRPCContext(event)).users.get(e.params.username),
            likes: appRouter.createCaller(await createTRPCContext(event)).likes.get(e.params.username)
        };
    }) satisfies PageServerLoad;
    

    After

    src/routes/user/[username]/+page.server.ts
    	import { trpcLoad } from '$lib/api/trpc-load';
    import type { PageServerLoad } from './$types';
     
    export const load = (async (e) => {
        return {
            user: trpcLoad(e, (t) => t.users.get(e.params.username)),
            likes: trpcLoad(e, (t) => t.likes.get(e.params.username))
        };
    }) satisfies PageServerLoad;
    

    Epic right? This looks so much cleaner and is much easier to maintain.

    In conclusion, tRPC combined with SvelteKit offers a powerful solution to manage server-side procedures.

    Hopefully this utility will help you optimize your code imports and avoid unexpected errors.