---
title: Example - Creating and editing posts using tRPC
description: Implementing tRPC in your Supabase Project
---

import Image from "next/image";

## Using tRPC in Your Application

### 1. Defining Routes and Mutations

In your `server/api/routers/posts.ts`, you define routes using `createTRPCRouter`. Each route can be a query or a mutation. For example, to create a post, you define a mutation like this:

```typescript
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { createClient } from "@/utils/supabase/server";
import { TRPCError } from "@trpc/server";

export const postsRouter = createTRPCRouter({
  create: protectedProcedure
    .input(z.object({ title: z.string().min(1), content: z.string().nullable() }))
    .mutation(async ({ input, ctx }) => {
      const { data, error } = await createClient()
        .from('posts')
        .insert({
          user_id: ctx.user.id,
          title: input.title,
          content: input.content,
          created_at: new Date().toISOString(),
          updated_at: new Date().toISOString(),
        })
        .select()
        .single();

      if (error) {
        console.error("Error creating post:", error);
        throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: error.message });
      }
      return data;
    }),
});
```

If you have already noticed, we use a **protectedProcedure** in the above example. This is because we use the `auth.session()` middleware in the `server/api/trpc/context.ts` file. This is a simple check to ensure that the user is authenticated before they can create or edit a post.

If you are using any database operation that requires authentication, you will need to use the `protectedProcedure` wrapper.

To edit a post, you would use a similar mutation with the necessary input parameters:

```typescript
  update: protectedProcedure
    .input(z.object({ id: z.number(), title: z.string().min(1), content: z.string().nullable() }))
    .mutation(async ({ input, ctx }) => {
      const { data, error } = await createClient()
        .from('posts')
        .update({ 
          title: input.title, 
          content: input.content, 
          updated_at: new Date().toISOString() 
        })
        .eq('id', input.id)
        .eq('user_id', ctx.user.id)
        .select()
        .single();

      if (error) {
        console.error("Error updating post:", error);
        throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: error.message });
      }
      return data;
    }),
```

And finally, to get all posts, you would use a query:

```typescript
  getAll: protectedProcedure
    .query(async ({ ctx }) => {
      const { data, error } = await createClient()
        .from('posts')
        .select('*')
        .eq('user_id', ctx.user.id)
        .order('created_at', { ascending: false });

      if (error) {
        console.error("Error fetching posts:", error);
        throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: error.message });
      }
      
      return data ?? [];
    }),
```

The structure of building API's with tRPC is simple. If you want to see the rest of the code for the posts router, you can view it [here](https://github.com/antoineross/hikari/server/api/routers/posts.ts).

### 2. Calling tRPC in the Client

In your client component, such as `components/posts.tsx`, you can call these routes using hooks provided by tRPC. For example, to fetch all posts, you would use:

```typescript
import { useQuery } from "@tanstack/react-query";
import { trpc } from "@/utils/trpc";

// Getting the posts

const { data: posts, isLoading, error: fetchError } = api.posts.getAll.useQuery()

// Creating a post
const createPost = api.posts.create.useMutation({
  onSuccess: async () => {
    await utils.posts.getAll.invalidate()
    setNewPost({ title: '', content: '' })
    toast({
      title: "Success",
      description: "Post created successfully!",
    })
  },
})

// Updating a post
const updatePost = api.posts.update.useMutation({
  onSuccess: async () => {
    await utils.posts.getAll.invalidate()
    setEditingPost(null)
    toast({
      title: "Success",
      description: "Post updated successfully!",
    })
  },
})

// Deleting a post
const deletePost = api.posts.delete.useMutation({
  onSuccess: async () => {
    await utils.posts.getAll.invalidate()
    toast({
      title: "Success",
      description: "Post deleted successfully!",
    })
  },
})
```

### 3. Benefits of Using tRPC

tRPC streamlines the development process by allowing you to define your API routes and types in one place, reducing the need for boilerplate code. This leads to quicker development cycles and a more cohesive codebase compared to traditional REST APIs. With tRPC, you can easily manage your API calls and ensure type safety across your application.

### Important Note

A new SQL file for the posts has been added. If anyone from a previous version of Hikari wants to try it locally, they need to run [this](https://github.com/antoineross/hikari/blob/main/supabase/migrations/20240918141953_posts.sql) SQL file to set up the necessary database structure.
