skip to Main Content

Hello there I’m using trpc and fastify and i have created an api end point to fetch data from prismic cms now I’m confused how to use this fetched data on my pages since I want to render my pages server side with pug template engine, when i go to /api/home I can see the data without issues, but I’m not sure how to extend my setup with trpc to allow my pages to use those fetched data

lib/trpc/index.ts

import { initTRPC } from '@trpc/server';
import superjson from 'superjson';

/**
 * Wrapper around TRPC
 *
 * TRPC is a typesafe way of making an API server and a client
 * The TypeScript types are shared between the two, keeping them in sync
 * The strength of TRPC is how quickly you can add new endpoints
 *
 * @see https://trpc.io
 */
export class Trpc {
    private readonly trpc = initTRPC.create({
        /**
         * @see https://trpc.io/docs/v10/data-transformers
         */
        transformer: superjson,
    });

    /**
     * @see https://trpc.io/docs/v10/router
     */
    public readonly router = this.trpc.router;

    /**
     * @see https://trpc.io/docs/v10/merging-routers
     */
    public readonly mergeRouters = this.trpc.mergeRouters;

    /**
     * @see https://trpc.io/docs/v10/procedures
     **/
    public readonly procedure = this.trpc.procedure;

    /**
     * @see https://trpc.io/docs/v10/middlewares
     */
    public readonly middleware = this.trpc.middleware;
}

lib/fastify/index.ts

import type { Router } from '@trpc/server';
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import type { AnyRouterDef } from '@trpc/server/dist/core/router';
import fastify from 'fastify';
import metricsPlugin from 'fastify-metrics';
import * as trpcPlayground from 'trpc-playground/handlers/fastify';

export interface ServerOptions {
    dev?: boolean;
    port?: number;
    prefix?: string;
}

/**
 * Wrapper around fastify
 *
 * @see https://www.fastify.io/
 */
export class Fastify {
    constructor(
        /**
         * The port
         */
        private readonly port: number,

        /**
         * The host
         */
        private readonly host: string,

        /**
         * Whether to run in development mode
         * Defaults to Env.NODE_ENV === 'development'
         */
        dev: boolean,

        /**
         * The fastify server being wrapped
         *
         * @dependencyinjection
         */
        public readonly server = fastify({ logger: dev })
    ) {}

    /**
     * Starts the fastify server
     */
    public readonly start = async () => {
        try {
            /**
             * @see https://www.fastify.io/docs/latest/Reference/Server/#listen
             */
            await this.server.listen({ port: this.port, host: this.host });
            console.log('listening on port', this.port);
        } catch (err) {
            this.server.log.error(err);
            process.exit(1);
        }
    };

    /**
     * Stop the fastify server
     */
    public readonly stop = async () => {
        await this.server.close();
    };

    /**
     * Registers metrics on fastify server
     */
    public readonly registerMetrics = async (endpoint = '/metrics') => {
        await this.server.register(metricsPlugin, { endpoint });
    };

    /**
     * Register a trpc router on fastify server
     * Include a playground endpoint if you want to use the playground
     */
    public readonly registerTrpc = async (
        prefix: string,
        appRouter: Router<AnyRouterDef>,
        playgroundEndpoint: string | undefined
    ) => {
        await this.server.register(fastifyTRPCPlugin, {
            prefix,
            trpcOptions: { router: appRouter },
        });
        if (playgroundEndpoint) {
            this.server.register(
                await trpcPlayground.getFastifyPlugin({
                    trpcApiEndpoint: prefix,
                    playgroundEndpoint,
                    router: appRouter,
                    request: {
                        superjson: true,
                    },
                }),
                { prefix: playgroundEndpoint }
            );
        }
    };
}

routes/home/index.ts

import { AbstractRoute } from '../abstract';
import { client } from '../../lib/prismic/client';

export class HomeRoute extends AbstractRoute {
    name = 'home';

    handler = this.trpc.procedure.query(async () => {
        try {
            const page = await client.getByUID('page', 'home');
            if (!page) throw new Error('Home page not found');
            return page;
        } catch (error: any) {
            throw new Error('Failed to fetch homepage: ' + error.message);
        }
    });
}

server.ts

import { Api } from '../api';
import { Env } from '../config/env';
import { Fastify } from '../lib/fastify';
import { Trpc } from '../lib/trpc';

/**
 * The top level server that instantiates the API and starts the server
 *
 * @example
 * const server = new Server()
 * await server.start()
 * await server.stop()
 */
export class Server {
    constructor(
        /**
         * The Env options for the server
         */
        private readonly env = Env.getEnv(),

        /**
         * The Trpc instance the API and routers will use
         *
         * @dependencyinjection
         */
        trpc = new Trpc(),

        /**
         * The API instance the server will use
         *
         * @dependencyinjection
         */
        private readonly api = new Api(trpc),

        /**
         * The Fastify instance the server will use to mount the API
         *
         * @dependencyinjection
         */
        private readonly fastifyServer: Fastify | undefined = undefined
    ) {}

    /**
     * Starts the server
     */
    public readonly start = async () => {
        const server = await this.init();
        return server.start();
    };

    /**
     * stops the server
     */
    public readonly stop = () => {
        return this.fastifyServer?.stop();
    };

    /**
     * Initializes the server if not yet initialized
     *
     * @returns the fastify server
     */
    private readonly init = async () => {
        if (this.fastifyServer) {
            return this.fastifyServer;
        }

        const fastifyServer = new Fastify(
            this.env.PORT,
            this.env.HOST,
            this.env.NODE_ENV === 'development'
        );

        await fastifyServer.registerMetrics();
        await fastifyServer.registerTrpc(
            this.env.TRPC_ENDPOINT,
            this.api.handler,
            this.env.TRPC_PLAYGROUND_ENDPOINT
        );

        return fastifyServer;
    };
}

index.ts

import type { Api } from './api';

export { Server } from './server';

export type AppRouter = Api['handler'];

2

Answers


  1. Since you want to call the tRPC appRouter server-side, and pass along data to your Pug templates/views, you’ll want to follow the second guide I linked in the comment: https://trpc.io/docs/server/server-side-calls

    In a nutshell, it’ll look like this (code not tested):

    // where/how you do this is dependent on what's available where
    // perhaps in your Api class?
    const createCaller = trpc.createCallerFactory(appRouter);
    
    // later, likely in some Fastify middleware/plugin
    request.apiCaller = createCaller({
      // some context for this request, e.g. current user
    });
    
    // and finally, within the route rendering the Pug template
    const data = await request.apiCaller.subRoute.someMethod({
      // some data
    });
    reply.view('home', { data });
    

    Though you may wish to move things around based on how you want to structure the code and where/how certain things, like the trpc instance and appRouter, are available.

    Login or Signup to reply.
  2. Using tRPC, Pug, and Fastify allows fetching the following data from the /api/home endpoint:

    Fetch data: Using tRPC, fetch the data from your HomeRoute:

    handler = this.trpc.procedure.query(async ({ res }) => {
        const page = await client.getByUID('page', 'home');
        if (!page) throw new Error('Home page not found');
        res.view('home.pug', { pageData: page });
        return page;
    });
    

    Rendering with Pug Make sure Fastify is configured using point of view plus using Pug template engine:

    import pointOfView from 'point-of-view';
    import pug from 'pug';
    
    this.server.register(pointOfView, {
        engine: { pug },
        root: path.join(__dirname, 'views'), // specify Pug template path
    });
    

    Access Data in Pug: In the Pug template, access the data like this:

    h1= pageData.title
    p= pageData.content
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search