Skip to main content

Introduction

Khedma Market uses tRPC to build a type-safe API layer between the client and server. This provides end-to-end type safety from your database to your frontend components without code generation.

Architecture

What is tRPC?

tRPC allows you to easily build & consume fully typesafe APIs without schemas or code generation. It leverages TypeScript to provide:
  • Type Safety: Full type inference from server to client
  • Auto-completion: IDE support for all API endpoints
  • Runtime Safety: Validation using Zod schemas
  • Developer Experience: No need to maintain separate API documentation

Core Components

The tRPC setup in Khedma Market consists of:
  1. Root Router (~/workspace/source/src/server/api/root.ts:13-23): Combines all feature routers
  2. Context (~/workspace/source/src/server/api/trpc.ts:29-36): Provides database and session access to procedures
  3. Procedures: Public and protected endpoints for each feature
  4. Client (~/workspace/source/src/trpc/react.tsx): React Query integration for the frontend

Base URL

The tRPC API is available at:
/api/trpc
The actual URL is determined by the environment:
  • Development: http://localhost:3000/api/trpc
  • Production: https://your-domain.com/api/trpc
  • Vercel: https://${VERCEL_URL}/api/trpc
See ~/workspace/source/src/trpc/shared.ts:8-15 for the implementation.

Available Routers

Khedma Market has 9 feature routers:

User Router

Namespace: user
Features:
  • User profile management
  • Personal information updates
  • Security settings (password, 2FA)
  • Username availability checking
  • Account role management (client/freelancer/company)
  • Company creation
Example procedures:
  • getUserbyUsername (protected)
  • updateUserPersonalInfo (protected)
  • updateUserSecurityInfo (protected)
  • checkUsername (protected)
  • updateAccount (protected)
  • createCompany (protected)

File Router

Namespace: file
Features:
  • Generate pre-signed S3 URLs for secure file uploads
  • Direct client-to-S3 upload flow
Example procedures:
  • generateUrl (protected)
View File API docs →

Order Router

Namespace: order
Features:
  • Order creation with package selection
  • Payment processing integration
  • Order status tracking
Example procedures:
  • create (protected)
View Order API docs →

Profile Router

Namespace: profile
Features:
  • Skills management (add, update, remove)
  • Languages management (add, update, remove)
  • User description updates
  • Public profile queries
Example procedures:
  • addSkill, updateSkill, removeSkill (protected)
  • addLanguage, updateLanguage, removeLanguage (protected)
  • getUserSkills, getLanguages (queries)
View Profile API docs →

Category Router

Namespace: category
Features:
  • Browse top-level categories
  • Get subcategories by parent ID
  • Hierarchical category structure
Example procedures:
  • getCategories (public)
  • getSubCategories (public)
View Category API docs →

Gig Router

Namespace: gig
Features:
  • Gig creation and drafts
  • Package management (basic, standard, premium)
  • Description and FAQ updates
  • Gallery/attachment management
  • Publishing and deletion
  • User gig listings
Example procedures:
  • createDraft (protected)
  • updateOverview (protected)
  • createPackages (protected)
  • updateDescriptionFaq (protected)
  • updateGallery (protected)
  • publish (protected)
  • delete (protected)
  • getUserGigs (protected)

Project Router

Namespace: project
Features:
  • Portfolio project creation
  • Draft workflow with gallery uploads
  • Project publishing and management
Example procedures:
  • createDraft (protected)
  • createProject (protected)
  • delete (protected)
View Project API docs →

Conversation Router

Namespace: conversation
Features:
  • Real-time messaging between users
  • Conversation creation and retrieval
  • Message sending with attachments
Example procedures:
  • getConverstion (protected)
  • sendMessage (protected)
View Conversation API docs →

Job Router

Namespace: job
Features:
  • Job posting creation and updates
  • Application submission
  • Job type management (full-time, part-time, contract, intern)
Example procedures:
  • create (protected)
  • update (protected)
  • apply (protected)
View Job API docs →

Making API Calls

Client-Side (React Components)

Khedma Market uses @trpc/react-query for client-side API calls. The API client is exported from ~/workspace/source/src/trpc/react.tsx:16.

Queries

import { api } from "@/trpc/react";

function UserProfile({ username }: { username: string }) {
  const { data, isLoading, error } = api.user.getUserbyUsername.useQuery({
    username,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>{data?.username}</div>;
}

Mutations

import { api } from "@/trpc/react";

function UpdateProfile() {
  const utils = api.useContext();
  const mutation = api.user.updateUserPersonalInfo.useMutation({
    onSuccess: () => {
      // Invalidate and refetch user data
      utils.user.getUserbyUsername.invalidate();
    },
  });

  const handleSubmit = (data: PersonalInfo) => {
    mutation.mutate(data);
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
    </form>
  );
}

Server-Side (Server Components)

For Next.js Server Components and API routes:
import { appRouter } from "@/server/api/root";
import { createTRPCContext } from "@/server/api/trpc";

export async function getUserData(username: string) {
  const context = await createTRPCContext({ headers: new Headers() });
  const caller = appRouter.createCaller(context);
  
  return await caller.user.getUserbyUsername({ username });
}

Error Handling

Error Types

tRPC uses specific error codes based on HTTP status codes:
  • UNAUTHORIZED: User is not authenticated (401)
  • FORBIDDEN: User lacks permission (403)
  • NOT_FOUND: Resource doesn’t exist (404)
  • BAD_REQUEST: Invalid input data (400)
  • UNPROCESSABLE_CONTENT: Valid input but cannot process (422)
  • INTERNAL_SERVER_ERROR: Server error (500)

Error Formatting

Errors include Zod validation details when input validation fails (~/workspace/source/src/server/api/trpc.ts:48-57):
{
  message: "Validation error",
  code: "BAD_REQUEST",
  data: {
    zodError: {
      fieldErrors: {
        email: ["Invalid email format"],
        password: ["Password too short"]
      }
    }
  }
}

Handling Errors in React

const mutation = api.user.updateAccount.useMutation({
  onError: (error) => {
    if (error.data?.code === "BAD_REQUEST") {
      // Handle validation errors
      const zodError = error.data.zodError;
      console.error(zodError?.fieldErrors);
    }
  },
});

Custom Error Throwing

In procedures, throw errors using TRPCError:
import { TRPCError } from "@trpc/server";

if (!user) {
  throw new TRPCError({
    code: "NOT_FOUND",
    message: "User not found",
  });
}

if (usernameExists) {
  throw new TRPCError({
    code: "BAD_REQUEST",
    message: "Username already taken",
    cause: { username: "Username already taken" },
  });
}
See examples in ~/workspace/source/src/server/api/routers/user.ts:76-80 and ~/workspace/source/src/server/api/routers/gig.ts:30-34.

Type Safety Benefits

Input/Output Type Inference

Khedma Market exports type helpers for inferring router types:
import { type RouterInput, type RouterOutput } from "@/server/api/root";

// Get input type for a procedure
type UpdateAccountInput = RouterInput["user"]["updateAccount"];

// Get output type for a procedure
type UserGigsOutput = RouterOutput["gig"]["getUserGigs"];
See ~/workspace/source/src/server/api/root.ts:27-28 for the implementation.

Zod Schema Validation

All inputs are validated using Zod schemas before reaching your procedure:
import { z } from "zod";

export const userRouter = createTRPCRouter({
  updateAccount: protectedProcedure
    .input(
      z.object({
        signUpAs: z.enum(["client", "freelancer", "company"]).default("client"),
        username: z.string(),
      }),
    )
    .mutation(async ({ input, ctx }) => {
      // input is fully typed and validated
      const { signUpAs, username } = input;
      // ...
    }),
});

SuperJSON Transformer

Khedma Market uses SuperJSON to serialize complex JavaScript types (Date, Map, Set, etc.) across the network without losing type information. This is configured in:
  • Server: ~/workspace/source/src/server/api/trpc.ts:47
  • Client: ~/workspace/source/src/trpc/shared.ts:6

Data Transformer

The API uses SuperJSON for data serialization, which allows:
  • Date objects: Automatically serialized/deserialized
  • undefined values: Preserved (unlike JSON)
  • BigInt, Map, Set: Supported types
// Server returns a Date object
return {
  createdAt: new Date(),
};

// Client receives actual Date object (not string)
const { data } = api.gig.getUserGigs.useQuery({ id });
console.log(data.createdAt instanceof Date); // true

Next Steps

Build docs developers (and LLMs) love