---
description: 
globs: apps/api/**/*.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
- **Frontend Integration**: React Query
- **Authentication**: Clerk
- **Validation**: Zod with zValidator
</tech_stack>

<project_structure>
apps/
  ├── api/
  │   ├── src/
  │   │   ├── modules/           # Feature-based modules
  │   │   │   ├── [module]/      # e.g., posts, webhooks
  │   │   │   │   ├── [module].routes.ts   # Route definitions
  │   │   │   │   └── [module].service.ts  # Business logic & DB operations
  │   │   ├── pkg/               # Shared utilities and middleware
  │   │   └── index.ts          # Main application setup
  └── web/
      └── src/
          └── api/
              └── [module].api.ts # React Query hooks

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].routes.ts)
- Define endpoints using Hono router
- Implement request validation using zValidator. You can leverage the created zod schemas from drizzle zod.
- Apply authentication middleware where needed
- Structure routes logically by resource
- Return consistent HTTP responses

Example route implementation:
```ts
const moduleRoutes = new Hono()
  .use(auth(),requireAuth)
  .get("/", async (c) => {
    const items = await moduleService.getItems();
    return c.json(items);
  })
  .post("/", zValidator("json", insertSchema), async (c) => {
    const data = c.req.valid("json");
    const userId = getUserId(c);
    const result = await moduleService.createItem({ ...data, userId });
    // If results could be undefined or otherwise, we can throw http 
    if (!result){
        // throw proper error code
        throw new HTTPException(404, {message: ...})
    }
    return c.json(result);
  });
```

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

</module_development_guidelines>

<frontend_integration>
When fetching data from the backend api on the client, use the following guidelines
`post.api.ts`
```ts
import { apiRpc, getApiClient, InferRequestType, callRpc } from "./client";

const $createPost = apiRpc.posts.$post;
// Simple get
export async function getPosts() {
  const client = await getApiClient();

  return callRpc(client.posts.$get());
}
// Safely leverage the typed params elsewhere within the nextjs application 
export type CreatePostParams = InferRequestType<typeof $createPost>["json"];
export async function createPost(params: CreatePostParams) {
  const client = await getApiClient();

  return callRpc(client.posts.$post({ json: params }));
}
```
</frontend_integration>
<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

### 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 with object inside, 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
- Follow RESTful conventions
- 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].routes.ts
3. Implement service logic in [module].service.ts
4. Add route to main application in index.ts
5. Create frontend integration in web/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>
Here's a complete example of a typical module:

(if required) create new schemas. 
// packages/db/schema.ts
.. new schema files

Then:
```ts
// posts.service.ts
import { db, posts, type NewPost } from "@repo/db";

export const postService = {
  async getPosts() {
    return db.select().from(posts);
  },

  async createPost(post: NewPost) {
    return db.insert(posts).values(post).returning();
  }
};

// posts.routes.ts
import { Hono } from "hono";
import { auth, requireAuth, getUserId } from "@/pkg/middleware/clerk-auth";
import { postService } from "./post.service";
import { zValidator } from "@/pkg/util/validator-wrapper";
import { postInsertSchema } from "@repo/db";

export const postRoutes = new Hono()
  .use(auth(),requireAuth)
  .get("/", async (c) => {
    const posts = await postService.getPosts();
    return c.json(posts);
  })
  .post("/", zValidator("json", postInsertSchema), async (c) => {
    const data = c.req.valid("json");
    const userId = getUserId(c);
    const post = await postService.createPost({ ...data, userId });
    return c.json(post);
  });
```

- Use the RPC-style API client for type-safe API calls
- Leverage `InferRequestType` for parameter typing
- Export plain async functions for API operations
- Optionally create React Query hooks when needed
```ts
// posts.api.ts (Frontend)
import { apiRpc, getApiClient, InferRequestType, callRpc } from "./client";

const $createPost = apiRpc.posts.$post;

export async function getPosts() {
  const client = await getApiClient();
  return  callRpc(client.posts.$get());
}

export async function createPost(params: InferRequestType<typeof $createPost>["json"]) {
  const client = await getApiClient();
  return callRpc(client.posts.$post({ json: params }));
  return response.json();
}
```
</example_api_workflow>


