Build A Full-Stack Typescript Application with Nuxt and tRPC

Aaron K Saunders - Aug 31 '23 - - Dev Community

tRpc - Move Fast and Break Nothing. End-to-end typesafe APIs made easy. Experience the full power of TypeScript inference to boost productivity for your NUXT full-stack application.

This blog is a companion to the video walkthrough of getting a Nuxt 3 application up and running with tRpc using the trpc-nuxt module.

This is the first video in the series, additional content will be created for you to follow along and build a Full-Stack Typescript Application with Nuxt and tRPC.

tPRC Nuxt Module - wobsoriano/trpc-nuxt Documentation

Installation

Create a Nuxt 3 App using the cli

npx nuxi@latest init trpc-nuxt-app
Enter fullscreen mode Exit fullscreen mode

Install the tRPC Libraries and Zod for schema and parameter validation.

npm install @trpc/server @trpc/client trpc-nuxt zod
Enter fullscreen mode Exit fullscreen mode

Configuration

modify nuxt.config.ts to transpile that into an ES5 bundle.

export default defineNuxtConfig({
  build: {
    transpile: ['trpc-nuxt']
  }
})
Enter fullscreen mode Exit fullscreen mode

Add Boilerplate Code

Entrypoint into trpc server; the embedded documentation explains what is going on.

// server/trpc/trpc.ts

/**
 * This is your entry point to setup the root configuration for tRPC on the server.
 * - `initTRPC` should only be used once per app.
 * - We export only the functionality that we use so we can enforce which base procedures should be used
 *
 * Learn how to create protected base procedures and other things below:
 * @see https://trpc.io/docs/v10/router
 * @see https://trpc.io/docs/v10/procedures
 */
import { initTRPC } from '@trpc/server'
import { Context } from '../trpc/context';

const t = initTRPC.context<Context>().create();

/**
 * Unprotected procedure
 **/
export const publicProcedure = t.procedure;

export const router = t.router;
export const middleware = t.middleware;
Enter fullscreen mode Exit fullscreen mode

The context, this is where you can put things that you want all routes to have access to. We will be putting out database client here so we can access it through the context in our routes

// server/trpc/context.ts

import { inferAsyncReturnType } from '@trpc/server';

/**
 * Creates context for an incoming request
 * @link https://trpc.io/docs/context
 */
export const createContext = () => ({});

export type Context = inferAsyncReturnType<typeof createContext>;
Enter fullscreen mode Exit fullscreen mode

The routes, the can be broken up into separate files so we will place an index file in the /trpc/routers directory to support that in the future, but for now we will just add the routes/endpoints directly to the router

// server/trpc/routers/index.ts

import { z } from 'zod';
import { publicProcedure, router } from '../trpc';

export const appRouter = router({
  hello: publicProcedure
    .input(
      z.object({
        text: z.string().nullish(),
      })
    )
    .query(({ input }) => {
      return {
        greeting: `hello ${input?.text ?? 'world'}`,
      };
    }),
});

// export type definition of API
export type AppRouter = typeof appRouter;
Enter fullscreen mode Exit fullscreen mode

The hello route has an optional string parameter text which is used in the query to return the greeting with the text parameter value or the static string hello.

Next create the tRPC Plugin so you can access the trpc client throughout your application.

You will be able to access the client using const { $trpcClient } = useNuxtApp(); anywhere in you nuxt app client.

// plugins/trpc-client.ts

import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client';
import type { AppRouter } from '~/server/trpc/routers';

export default defineNuxtPlugin(() => {
  /**
   * createTRPCNuxtClient adds a `useQuery` composable
   * built on top of `useAsyncData`.
   */
  const trpcClient = createTRPCNuxtClient<AppRouter>({
    links: [
      httpBatchLink({
        url: '/api/trpc',
      }),
    ],
  });

  return {
    provide: {
      trpcClient,
    },
  };
});
Enter fullscreen mode Exit fullscreen mode

Next we create and initialize the API Handler using theappRouter we create and we pass in the function to create the context

// server/api/trpc/[trpc].ts

import { createNuxtApiHandler } from 'trpc-nuxt';
import { appRouter } from '../../trpc/routers';
import { createContext } from '../../trpc/context';

// export API handler
export default createNuxtApiHandler({
  router: appRouter,
  createContext,
});
Enter fullscreen mode Exit fullscreen mode

Test Code In Application

Now that everything is in place we can test the api call in the app.vue

Code to call api from client

// app.vue

<script setup lang="ts">
const { $trpcClient } = useNuxtApp();
console.log($trpcClient);

const { data } = await $trpcClient.hello.useQuery({text : "smith"});
</script>

<template>
  <p>app</p>
  <div>
    <p>{{ data?.greeting }}</p>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Source Code

GitHub logo aaronksaunders / trpc-nuxt-video-app

starter template for trpc-nuxt module integrated into a nuxt app

Nuxt 3 tRPC-Nuxt Minimal Starter Template

Look at the Blog Post and YouTube Video to learn more.

Move Fast and Break Nothing. End-to-end typesafe APIs made easy. Experience the full power of TypeScript inference to boost productivity for your NUXT full-stack application.

Use this project to get a Nuxt 3 application up and running with tRpc using the trpc-nuxt module.

Setup

Make sure to install the dependencies:

# npm
npm install
Enter fullscreen mode Exit fullscreen mode

Development Server

Start the development server on http://localhost:3000:

# npm
npm run dev
Enter fullscreen mode Exit fullscreen mode

Links

Social Media

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .