---
description: Guidelines for creating typesafe apis on hono restful API.
globs: apps/**/*.ts
alwaysApply: false
---
# Backend API Architecture and Development Guidelines

You are an expert TypeScript backend engineer specializing in building modern, type-safe APIs. Your expertise covers Hono for HTTP routing, Drizzle ORM for database operations, and React Query for frontend integration.

<core_architecture>

<tech_stack>
- **Server & Routing**: Hono
- **Database ORM**: Drizzle with PostgreSQL
- **RPCs**: TRPC
- **Frontend Integration**: React Query
- **Authentication**: Clerk
- **Validation**: Zod with zValidator
</tech_stack>

<project_structure>
apps/
  ├── backend/
      ├── src/
          ├── modules/           # Feature-based modules
          │   ├── [module]/      # e.g., posts, webhooks
          │   │   ├── [module].router.ts   # Route definitions
          │   │   └── [module].service.ts  # Business logic & DB operations
          ├── pkg/               # Shared utilities and middleware
          └── index.ts          # Main application setup; must list new TRPC modules here.

packages/
  └── db/
      ├── src/
      │   ├── schema.ts         # Database schema definitions
      │   ├── types.ts          # Shared TypeScript types
      │   ├── index.ts          # Main exports
      │   └── util/             # Database utilities
</project_structure>

<module_development_guidelines>
### 1. Database layer
- If the request needs a new column or addition database fields start by creating and updating schema.ts
- Leverage types.ts inside packages/db to create new zod schemas for the newly created tables or columns and eg:
```
export type Post = InferSelectModel<typeof schema.posts>;
export type NewPost = InferInsertModel<typeof schema.posts>;

export const postInsertSchema = createInsertSchema(schema.posts).omit({ userId: true });
export const postSelectSchema = createSelectSchema(schema.posts);
```


### 2. Service Layer ([module].service.ts)
- Implement business logic
- Handle database operations using Drizzle
- Return strongly typed responses
- Keep services focused and modular
- Import db from the `packages/db` 

Example service implementation:
```ts
`[module].service.ts`
import { db, eq, items} from "@repo/db"

export const moduleService = {
  async getItems() {
    return db.select().from(items);
  },
  
  async createItem(data: NewItem) {
    return db.insert(items).values(data).returning();
  }
};
```


### 3. Route Layer ([module].router.ts)
- Define endpoints using TRPC

After the route is created, you must add it to the `apps/backend/src/index.ts` route so its accessable by the frontend.

</module_development_guidelines>

<package_management>

- Use `pnpm` as the primary package manager for the project
- Install dependencies using `pnpm add [package-name]`
- Install dev dependencies using `pnpm add -D [package-name]`
- Install workspace dependencies using `pnpm add -w [package-name]`
</package_management>

<development_guidelines>
### Running Scripts
- Use `bun` as the runtime environment and script runner
- Execute scripts defined in package.json using `bun run [script-name]`
- Run TypeScript files directly using `bun [file.ts]`

### Monorepo 
The project use turbo repo. To run everything, use the `turbo dev` script from the root folder.

### Type Safety
- Use Drizzle schemas for database types
- Share types between frontend and backend using a shared package
- Leverage zod for runtime validation
- Use TRPC for RPC type-safety

### Error Handling
- Implement consistent error handlers
- Use proper HTTP status codes
- Return structured error responses
- Handle edge cases appropriately
- Use the logger from the package @repo/logger, i.e `logger.error` instead of `console.error`

### Authentication & Authorization
- Use Clerk middleware for authentication
- Implement role-based access control where needed
- Validate user permissions at the route level
- Keep authentication logic in middleware

### API Design Principles
- Use TRPC for API connections between frontend and backend
- Use consistent naming patterns
- Implement proper request validation
- Structure endpoints by resource/module
- Keep routes clean and delegate logic to services

### Database Operations
- Use Drizzle for all database interactions
- Implement proper migrations
- Handle transactions when needed
- Write efficient queries
- Use appropriate indexes
</development_guidelines>

<dev_workflow>
### Creating a New Module for different buisness logic
1. Create module directory in api/src/modules/[module]
2. Define routes in [module].router.ts
3. Implement service logic in [module].service.ts
4. Add route to main application in index.ts
5. Create frontend integration in frontend/src/api/[module].api.ts
</dev_workflow>

### Testing Requirements
- If needed use vitest to create testing files inside of the modules, [module].test.ts 
- 
### Code Quality
- For methods with more than one argument, use object destructuring: `function myMethod({ param1, param2 }: MyMethodParams) {...}`.
</best_practices>


<example_api_workflow>
Router file:

tenants.router.ts
```ts
import { publicProcedure, router } from "../../trpc";
import { tenantsService } from "./tenants.service";
import { z } from "zod";

// Define the Tenant schema
const TenantSchema = z.object({
  id: z.string(),
  name: z.string(),
  createdAt: z.date(),
  updatedAt: z.date().nullable(),
});

export const tenantsRouter = router({
  list: publicProcedure
    .output(z.array(TenantSchema))
    .query(async () => {
      return tenantsService.list();
    }),

  create: publicProcedure
    .input(z.object({ name: z.string().min(1) }))
    .output(TenantSchema)
    .mutation(async ({ input }) => {
      const result = await tenantsService.create(input);
      if (!result) throw new Error('Failed to create tenant');
      return result;
    }),
}); 
```

tenants.service.ts
```ts
import { db, tenants } from "@repo/db";
import { eq } from "drizzle-orm";

type NewTenant = typeof tenants.$inferInsert;

export const tenantsService = {
  async list() {
    try {
      const results = await db.select().from(tenants);
      return results ?? [];
    } catch (error) {
      console.error(error);
      throw error;
    }
  },

  async create(data: Omit<NewTenant, "id" | "createdAt" | "updatedAt">) {
    try {
      const result = await db
        .insert(tenants)
        .values({
          id: crypto.randomUUID(),
          ...data,
          createdAt: new Date(),
          updatedAt: new Date(),
        })
        .returning();
      return result[0];
    } catch (error) {
      console.error(error);
      throw error;
    }
  },
}; 
```

tenants/page.tsx
```ts
"use client";

import { useTRPC } from "@/utils/trpc";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { useState } from "react";
import { Skeleton } from "@/components/ui/skeleton";

export default function TenantsPage() {
  const trpc = useTRPC();
  const queryClient = useQueryClient();
  const { data: tenants, isLoading, error } = useQuery(trpc.tenants.list.queryOptions());
  
  const createMutation = useMutation({
    ...trpc.tenants.create.mutationOptions(),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: trpc.tenants.list.queryKey() });
    }
  });

  const createTenant = () => createMutation.mutate({ name: "New Tenant" });

  ...

  <Button onClick={createTenant}>Add Tenant</Button>

  ...
```
</example_api_workflow>


