My First T3 App
Tags: trpc, typescript, t3stack, learninginpublic
Published: 2022-10-26 09:20:42

---
title: My First T3 App
published: true
date: 2022-10-26 20:00:00 UTC
cover_image: https://images.unsplash.com/photo-1594085736938-c9348bbd566e?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=420&w=1000
tags: trpc, typescript, t3stack, learninginpublic
canonical_url: https://www.rockyourcode.com/my-first-t3-app/
---

[tRPC][trpc] is the hottest new thing in the TypeScript ecosystem: build end-to-end **type-safe** APIs without the overhead of GraphQL.

tRPC is a protocol to expose a function of your backend to your frontend using TypeScript type definitions.  
No code generation required. You write both your backend and your frontend with TypeScript and share the types.


tRPC is framework-agnostic.

[Create-t3-app][t3] is build on top of tRPC. It offers an opinionated starter template that helps with building a complete web application with [Next.js](https://nextjs.org) and Prisma.

This blog post chronicles my journey in creating my first T3 app. Let's see how the T3 stack works!

## Create Application

```bash
pnpm dlx create-t3-app@latest
```

The command guides you through the installation process and allows you to choose a few options (trpc, prisma, next-auth, tailwind).

I am happy to see that the command also works with [pnpm](https://pnpm.io) out of the box.

The command bootstraps the application. At the end of the process, there is a hint on what commands to run:

```bash
cd my-t3-app
pnpm install
pnpm prisma db push
pnpm dev
```

The project also offers a `README` file with minimal information to get you started.

## Prisma

My application should show cat pictures because the internet loves cats.

Let's adjust the Prisma schema:

```diff
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

+model Cat {
+  id        String   @id @default(cuid())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  imageUrl  String
+}
```

This looks like a minimal example for a first application. Run `pnpm exec prisma migrate dev --name add_cat_model`.

## tRPC Router

My next instinct is to hook up the trpc router. The project comes with an example router in `src/server/router/example.ts`. I'll adjust that to be a cat router.

The router uses [zod][zod], a schema-validation library, to build a router.

The example query has an input parameter of the String type.  
For my case, I want a random cat picture, so no input is needed. Can I just delete the input parameter and return a random cat?

Before:

```ts
import { createRouter } from './context'
import { z } from 'zod'

export const exampleRouter = createRouter()
  .query('hello', {
    input: z
      .object({
        text: z.string().nullish(),
      })
      .nullish(),
    resolve({ input }) {
      return {
        greeting: `Hello ${input?.text ?? 'world'}`,
      }
    },
  })
  .query('getAll', {
    async resolve({ ctx }) {
      return await ctx.prisma.example.findMany()
    },
  })
```

After:

```ts
import { createRouter } from './context'
import { Cat } from '@prisma/client'

export const catRouter = createRouter()
  .query('random', {
    async resolve({ ctx }) {
      const randomCats = await ctx.prisma.$queryRaw<Cat[]>`SELECT id, imageUrl
                                                           FROM Cat
                                                           ORDER BY RANDOM()
                                                           LIMIT 1`
      return randomCats[0]
    },
  })
  .query('getAll', {
    async resolve({ ctx }) {
      return await ctx.prisma.cat.findMany()
    },
  })
```

I use a [raw SQL query][queryraw] to retrieve a random cat from the database and add a typing for `Cat[]`.  
That's not pretty and does not give me the advantage of using the schema validator, but Prisma doesn't implement [getting a random record](https://github.com/prisma/prisma/issues/5894). So raw SQL it is!

The raw query returns an array in any case, so we select the first element and return it.

## Seed Script

Before I try to hook up the frontend, I remember that I don't have any example data in my database.

Luckily, the Prisma documentation can [help me][seed].

Add a new entry to `package.json`:

```json
{
  "prisma": {
    "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
  }
}
```

Create a new seed script in the `prisma` folder (`prisma/seed.ts`):

```ts
import { PrismaClient } from '@prisma/client'
import { fetch } from 'next/dist/compiled/@edge-runtime/primitives/fetch'

const prisma = new PrismaClient()

async function main() {
  const requests = Array(10)
    .fill('https://aws.random.cat/meow')
    .map((url) => fetch(url))

  Promise.all(requests)
    // map array of responses into an array of response.json() to read their content
    .then((responses) => Promise.all(responses.map((r) => r.json())))
    // insert all responses as imageUrl
    .then((cats) =>
      cats.forEach(
        async (cat) => await prisma.cat.create({ data: { imageUrl: cat.file } })
      )
    )
}

main()
  .then(async () => {
    await prisma.$disconnect()
  })
  .catch(async (e) => {
    console.error(e)
    await prisma.$disconnect()
    process.exit(1)
  })
```

I fetch ten image URLs from an API that offers random cat images and insert them into the database. Quite ugly, but it works.

In my terminal, I run type the following command:

```bash
pnpm exec prisma db seed
```

Success!

## Hook Up the Client

Finally, we can try to show this data on the browser.

After ripping out the example router and replacing it with my cat router, I check `src/pages/index.tsx`.

It has some boilerplate which I adjust to my needs:

```tsx
import type { NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'
import { trpc } from '../utils/trpc'

const Home: NextPage = () => {
  const { data: cat } = trpc.useQuery(['cat.random'])

  return (
    <div style={{ display: 'grid', placeItems: 'center' }}>
      <Head>
        <title>T3 Cats</title>
        <meta name="T3 cats" content="Generated by create-t3-app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div>
        <h1 style={{ textAlign: 'center' }}>
          Create <span>T3</span> App
        </h1>

        <section>
          <div>
            {cat ? (
              <Image
                src={cat.imageUrl}
                alt={`random cat ${cat.id}`}
                layout={'fixed'}
                width={300}
                height={300}
              />
            ) : (
              <p>Loading...</p>
            )}
          </div>
        </section>
      </div>
    </div>
  )
}

export default Home
```

That was surprisingly easy, especially if you are familiar with Prisma.

## First Impressions

The starter template does a good job on guiding you through the process.

The examples are enough to paint a broad picture on how trpc with Next.js works. Familiarity with prisma is assumed.

You might need to consult the Prisma documentation, trpc is almost self-declaratory, Prisma is not.

## Links

- [tRPC][trpc]
- [Create T3 App][t3]

[trpc]: https://trpc.io
[t3]: https://create.t3.gg/
[zod]: https://github.com/colinhacks/zod
[queryraw]: https://www.prisma.io/docs/concepts/components/prisma-client/raw-database-access
[seed]: https://www.prisma.io/docs/guides/database/seed-database